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

EditorForm.WindowChrome.cs

238 строк · 9,187 байт · модуль UI
  1using System;
  2using System.Collections.Generic;
  3using System.Drawing;
  4using System.Drawing.Drawing2D;
  5using System.Drawing.Imaging;
  6using System.Drawing.Text;
  7using System.IO;
  8using System.Linq;
  9using System.Runtime.InteropServices;
 10using System.Windows.Forms;
 11using WindowCapture.App;
 12using WindowCapture.Detection;
 13using WindowCapture.Effects;
 14using WindowCapture.Helpers;
 15using WindowCapture.Integration;
 16using WindowCapture.Models;
 17using WindowCapture.Native;
 18
 19namespace WindowCapture.UI
 20{
 21    public partial class EditorForm
 22    {
 23        // ===== Window chrome (settings, dispose, blur, WndProc) =====
 24
 25        private void ShowSettings()
 26        {
 27            using (var dialog = new SettingsDialog())
 28            {
 29                dialog.ShowDialog(this);
 30            }
 31            CreateBlurredBitmap();
 32            EnableBlurBackground();
 33            ApplyRoundedCorners();
 34            canvas.Invalidate();
 35            Invalidate();
 36        }
 37
 38        protected override void OnFormClosed(FormClosedEventArgs e)
 39        {
 40            IsOpen = false;
 41            base.OnFormClosed(e);
 42        }
 43
 44        protected override void Dispose(bool disposing)
 45        {
 46            if (disposing)
 47            {
 48                IsOpen = false;
 49                D2DRenderer.Dispose();
 50                if (zoomTimer != null) { zoomTimer.Stop(); zoomTimer.Dispose(); }
 51                if (glassLeaveTimer != null) { glassLeaveTimer.Stop(); glassLeaveTimer.Dispose(); }
 52                if (zoomFadeTimer != null) { zoomFadeTimer.Stop(); zoomFadeTimer.Dispose(); }
 53                if (fileNameFadeTimer != null) { fileNameFadeTimer.Stop(); fileNameFadeTimer.Dispose(); }
 54                if (videoUpdateTimer != null) { videoUpdateTimer.Stop(); videoUpdateTimer.Dispose(); }
 55                if (videoControlsFadeTimer != null) { videoControlsFadeTimer.Stop(); videoControlsFadeTimer.Dispose(); }
 56                if (spaceHoldTimer != null) { spaceHoldTimer.Stop(); spaceHoldTimer.Dispose(); }
 57                if (delayedNameTimer != null) { delayedNameTimer.Stop(); delayedNameTimer.Dispose(); }
 58                if (wordPanelWindow != null && !wordPanelWindow.IsDisposed) wordPanelWindow.Close();
 59                // Stop GIF animation
 60                if (canvas != null) canvas.StopGifAnimation();
 61                if (gifImage != null) { gifImage.Dispose(); gifImage = null; }
 62                // Stop video playback
 63                DsCleanup();
 64                if (videoOverlayForm != null) { videoOverlayForm.Close(); videoOverlayForm.Dispose(); videoOverlayForm = null; }
 65                if (videoOverlay != null) videoOverlay.Dispose();
 66                if (dsPanel != null) dsPanel.Dispose();
 67                if (videoBrowser != null) videoBrowser.Dispose();
 68                // Dispose image cache
 69                if (imageCache != null)
 70                {
 71                    lock (imageCache) { foreach (var bmp in imageCache.Values) bmp.Dispose(); imageCache.Clear(); }
 72                }
 73                if (captured != null) captured.Dispose();
 74                if (blurredBitmap != null) blurredBitmap.Dispose();
 75                if (clipPath != null) clipPath.Dispose();
 76                if (canvas != null) canvas.Dispose();
 77                // Release Word COM references acquired during this session so Word.exe doesn't linger.
 78                try { WordIntegration.Cleanup(); } catch { }
 79            }
 80            base.Dispose(disposing);
 81        }
 82
 83        private bool isWin10RoundedCorners;
 84
 85        public void ApplyRoundedCorners()
 86        {
 87            int cornerRadius = Settings.CornerRadius;
 88            if (cornerRadius <= 0)
 89            {
 90                Region = null;
 91                isWin10RoundedCorners = false;
 92                return;
 93            }
 94            // Try Windows 11 native rounded corners first
 95            if (!WinApi.TryEnableRoundedCorners(Handle, WinApi.DWMWCP_ROUND))
 96            {
 97                // Windows 10: don't use Region (DWM blur ignores Region and extends
 98                // to full rectangular window). Instead, we handle corners visually
 99                // in ScrollContainer_Paint by painting opaque tint in corner areas
100                // and semi-transparent tint in the rounded interior.
101                isWin10RoundedCorners = true;
102                Region = null;
103            }
104            else
105            {
106                isWin10RoundedCorners = false;
107            }
108        }
109
110        private void EnableBlurBackground()
111        {
112            BlurHelper.Apply(Handle);
113        }
114
115        protected override void OnPaintBackground(PaintEventArgs e)
116        {
117            // Clear to fully transparent (alpha=0) so DWM blur composition shows through
118            e.Graphics.Clear(Color.FromArgb(0, 0, 0, 0));
119        }
120
121        protected override void OnPaint(PaintEventArgs e)
122        {
123            base.OnPaint(e);
124        }
125
126        // Enable window resizing for borderless form
127        protected override CreateParams CreateParams
128        {
129            get
130            {
131                CreateParams cp = base.CreateParams;
132                cp.Style |= 0x00040000 | 0x02000000; // WS_SIZEBOX | WS_CLIPCHILDREN
133                return cp;
134            }
135        }
136
137        // Constants for window resize with WndProc
138        private const int RESIZE_BORDER = 8;
139        private const int WM_NCACTIVATE = 0x0086;
140        private const int WM_NCCALCSIZE = 0x0083;
141        private const int WM_NCHITTEST = 0x0084;
142        private const int HTLEFT = 10;
143        private const int HTRIGHT = 11;
144        private const int HTTOP = 12;
145        private const int HTTOPLEFT = 13;
146        private const int HTTOPRIGHT = 14;
147        private const int HTBOTTOM = 15;
148        private const int HTBOTTOMLEFT = 16;
149        private const int HTBOTTOMRIGHT = 17;
150
151        private const int WM_ERASEBKGND = 0x0014;
152        private const int WM_GETMINMAXINFO = 0x0024;
153
154        [StructLayout(LayoutKind.Sequential)]
155        private struct MINMAXINFO
156        {
157            public Point ptReserved;
158            public Point ptMaxSize;
159            public Point ptMaxPosition;
160            public Point ptMinTrackSize;
161            public Point ptMaxTrackSize;
162        }
163
164        protected override void WndProc(ref Message m)
165        {
166            // Constrain maximized size to current monitor's working area
167            if (m.Msg == WM_GETMINMAXINFO)
168            {
169                try
170                {
171                    var info = (MINMAXINFO)Marshal.PtrToStructure(m.LParam, typeof(MINMAXINFO));
172                    var screen = Screen.FromHandle(Handle);
173                    var wa = screen.WorkingArea;
174                    info.ptMaxPosition = new Point(wa.X - screen.Bounds.X, wa.Y - screen.Bounds.Y);
175                    info.ptMaxSize = new Point(wa.Width, wa.Height);
176                    Marshal.StructureToPtr(info, m.LParam, false);
177                }
178                catch { }
179                base.WndProc(ref m);
180                return;
181            }
182
183            // Suppress background erase to eliminate flicker during resize
184            if (m.Msg == WM_ERASEBKGND)
185            {
186                m.Result = (IntPtr)1;
187                return;
188            }
189
190            // Prevent white border artifacts on focus change
191            if (m.Msg == WM_NCACTIVATE)
192            {
193                m.Result = (IntPtr)1;
194                return;
195            }
196
197            // Force zero non-client area - removes the DWM-drawn 1px light border
198            // on Windows 10 when WS_SIZEBOX is set with FormBorderStyle.None
199            if (m.Msg == WM_NCCALCSIZE && m.WParam != IntPtr.Zero)
200            {
201                m.Result = IntPtr.Zero;
202                return;
203            }
204
205            if (m.Msg == WM_NCHITTEST)
206            {
207                // Handle completely ourselves - don't call base for borderless form
208                // Extract screen coordinates (signed 16-bit values)
209                int x = (short)(m.LParam.ToInt64() & 0xFFFF);
210                int y = (short)((m.LParam.ToInt64() >> 16) & 0xFFFF);
211                Point pt = PointToClient(new Point(x, y));
212
213                bool left = pt.X < RESIZE_BORDER;
214                bool right = pt.X > ClientSize.Width - RESIZE_BORDER;
215                bool top = pt.Y < RESIZE_BORDER;
216                bool bottom = pt.Y > ClientSize.Height - RESIZE_BORDER;
217
218                if (top && left) m.Result = (IntPtr)HTTOPLEFT;
219                else if (top && right) m.Result = (IntPtr)HTTOPRIGHT;
220                else if (bottom && left) m.Result = (IntPtr)HTBOTTOMLEFT;
221                else if (bottom && right) m.Result = (IntPtr)HTBOTTOMRIGHT;
222                else if (left) m.Result = (IntPtr)HTLEFT;
223                else if (right) m.Result = (IntPtr)HTRIGHT;
224                else if (top) m.Result = (IntPtr)HTTOP;
225                else if (bottom) m.Result = (IntPtr)HTBOTTOM;
226                else m.Result = (IntPtr)1;  // HTCLIENT
227                return;
228            }
229            base.WndProc(ref m);
230        }
231
232        // Public method to refresh blurred bitmap after effects
233        public void RefreshBlurredBitmap()
234        {
235            CreateBlurredBitmap();
236        }
237    }
238}