Spring – Rev 1

Subversion Repositories:
Rev:
//  From xDasEinhorn @ https://www.mpgh.net/forum/showthread.php?t=1086623

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DXGI;
using Spring.Utilities;
using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Factory = SharpDX.Direct2D1.Factory;
using Point = System.Drawing.Point;

namespace Spring.HUD
{
    /// <summary>
    ///     Fix for VS not loading classes derived from abstract classes.
    /// </summary>
    /// <typeparam name="TAbstract">the abstract class</typeparam>
    /// <typeparam name="TBase">the derived base</typeparam>
    /// <remarks>https://stackoverflow.com/questions/481305/the-designer-must-create-an-instance-of-cannot-because-the-type-is-declared-ab</remarks>
    public class AbstractControlDescriptionProvider<TAbstract, TBase> : TypeDescriptionProvider
    {
#region Constructors, Destructors and Finalizers

        public AbstractControlDescriptionProvider()
            : base(TypeDescriptor.GetProvider(typeof(TAbstract)))
        {
        }

#endregion

#region Public Methods

        public override Type GetReflectionType(Type objectType, object instance)
        {
            if (objectType == typeof(TAbstract))
            {
                return typeof(TBase);
            }

            return base.GetReflectionType(objectType, instance);
        }

        public override object CreateInstance(IServiceProvider provider,
                                              Type objectType,
                                              Type[] argTypes,
                                              object[] args)
        {
            if (objectType == typeof(TAbstract))
            {
                objectType = typeof(TBase);
            }

            return base.CreateInstance(provider, objectType, argTypes, args);
        }

#endregion
    }

    [TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<DirectXOverlay, Form>))]
    public abstract partial class DirectXOverlay : Form
    {
#region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private ThreadPriority ThreadPriority { get; }

        private bool BackgroundThread { get; }

        private WindowRenderTarget _device;

        private ManualResetEvent _formLoadManualResetEvent;

        private volatile bool _run;

        private Thread _sDx;

#endregion

#region Constructors, Destructors and Finalizers

        protected DirectXOverlay(ThreadPriority priority, bool backgroundThread) : this()
        {
            ThreadPriority = priority;
            BackgroundThread = backgroundThread;
        }

        protected DirectXOverlay()
        {
            _formLoadManualResetEvent = new ManualResetEvent(false);

            InitializeComponent();

            Show();
        }

        /// <summary>
        ///     Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && components != null)
            {
                _formLoadManualResetEvent?.Dispose();
                _formLoadManualResetEvent = null;

                components.Dispose();
            }

            base.Dispose(disposing);
        }

#endregion

#region Event Handlers

        private void SpringDirectXOverlay_Load(object sender, EventArgs e)
        {
            _device = Initialize(this);

            _formLoadManualResetEvent.Set();
        }

#endregion

#region Private Methods

        private static WindowRenderTarget Initialize(IWin32Window form)
        {
            var factory = new Factory();

            var renderProperties = new HwndRenderTargetProperties
                                   {
                                       Hwnd = form.Handle,
                                       PixelSize = new Size2(1920, 1080),
                                       PresentOptions = PresentOptions.None
                                   };

            // caution DirectX error
            //SetLayeredWindowAttributes(this.Handle, 0, 255, Managed.LWA_ALPHA);

            //Init DirectX
            return new WindowRenderTarget(factory,
                new RenderTargetProperties(new PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied)),
                renderProperties);
        }

        protected void Start(TimeSpan abortTime)
        {
            StopRenderThread(abortTime);
            StartRenderThread();
        }

        private void StartRenderThread()
        {
            _run = true;
            _sDx = new Thread(SDxThread) { Priority = ThreadPriority, IsBackground = BackgroundThread };
            _sDx.Start(_device);
        }

        private void StopRenderThread(TimeSpan abortTime)
        {
            _run = false;

            try
            {
                if (_sDx != null && !_sDx.Join(abortTime))
                {
                    _sDx.Abort();
                }
            }
            catch (ThreadStateException)
            {
            }
            catch
            {
                //TODO: handle unforeseen errors
            }
            finally
            {
                _sDx = null;
            }
        }

        protected void Stop(TimeSpan abortTime)
        {
            StopRenderThread(abortTime);
        }

        private protected abstract void SDxRendering(WindowRenderTarget render);

        protected override void OnPaint(PaintEventArgs e) // create the whole form
        {
            int[] margins = { 0, 0, Width, Height };
            DirectXOverlayNative.DwmExtendFrameIntoClientArea(Handle, ref margins);

            // Set click through.
            var initialStyle = DirectXOverlayNative.GetWindowLong(Handle, -20);
            DirectXOverlayNative.SetWindowLong(Handle, -20, initialStyle | 0x80000 | 0x20);
        }

        private void SDxThread(object sender)
        {
            _formLoadManualResetEvent.WaitOne(-1);

            do
            {
                try
                {
                    SDxRendering((WindowRenderTarget) sender);
                }
                catch (ThreadAbortException)
                {
                    // thread aborted, so quit
                }
                catch (Exception)
                {
                    // Prevent UAC from breaking the HUD.
                    StopRenderThread(TimeSpan.FromSeconds(1));

                    this.InvokeIfRequired(form =>
                        {
                            _device = Initialize(form);
                            StartRenderThread();
                        });
                }
            } while (_run);
        }

#endregion

#region Nested Types

        internal class DirectXOverlayNative
        {
#region Public Enums, Properties and Fields

            [Flags]
            public enum SetWindowPosFlags : uint
            {
                /// <summary>
                ///     If the calling thread and the thread that owns the window are attached to different input queues,
                ///     the system posts the request to the thread that owns the window. This prevents the calling thread from
                ///     blocking its execution while other threads process the request.
                /// </summary>
                /// <remarks>SWP_ASYNCWINDOWPOS</remarks>
                SynchronousWindowPosition = 0x4000,

                /// <summary>Prevents generation of the WM_SYNCPAINT message.</summary>
                /// <remarks>SWP_DEFERERASE</remarks>
                DeferErase = 0x2000,

                /// <summary>Draws a frame (defined in the window's class description) around the window.</summary>
                /// <remarks>SWP_DRAWFRAME</remarks>
                DrawFrame = 0x0020,

                /// <summary>
                ///     Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to
                ///     the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE
                ///     is sent only when the window's size is being changed.
                /// </summary>
                /// <remarks>SWP_FRAMECHANGED</remarks>
                FrameChanged = 0x0020,

                /// <summary>Hides the window.</summary>
                /// <remarks>SWP_HIDEWINDOW</remarks>
                HideWindow = 0x0080,

                /// <summary>
                ///     Does not activate the window. If this flag is not set, the window is activated and moved to the
                ///     top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter
                ///     parameter).
                /// </summary>
                /// <remarks>SWP_NOACTIVATE</remarks>
                DoNotActivate = 0x0010,

                /// <summary>
                ///     Discards the entire contents of the client area. If this flag is not specified, the valid
                ///     contents of the client area are saved and copied back into the client area after the window is sized or
                ///     repositioned.
                /// </summary>
                /// <remarks>SWP_NOCOPYBITS</remarks>
                DoNotCopyBits = 0x0100,

                /// <summary>Retains the current position (ignores X and Y parameters).</summary>
                /// <remarks>SWP_NOMOVE</remarks>
                IgnoreMove = 0x0002,

                /// <summary>Does not change the owner window's position in the Z order.</summary>
                /// <remarks>SWP_NOOWNERZORDER</remarks>
                DoNotChangeOwnerZOrder = 0x0200,

                /// <summary>
                ///     Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to
                ///     the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent
                ///     window uncovered as a result of the window being moved. When this flag is set, the application must
                ///     explicitly invalidate or redraw any parts of the window and parent window that need redrawing.
                /// </summary>
                /// <remarks>SWP_NOREDRAW</remarks>
                DoNotRedraw = 0x0008,

                /// <summary>Same as the SWP_NOOWNERZORDER flag.</summary>
                /// <remarks>SWP_NOREPOSITION</remarks>
                DoNotReposition = 0x0200,

                /// <summary>Prevents the window from receiving the WM_WINDOWPOSCHANGING message.</summary>
                /// <remarks>SWP_NOSENDCHANGING</remarks>
                DoNotSendChangingEvent = 0x0400,

                /// <summary>Retains the current size (ignores the cx and cy parameters).</summary>
                /// <remarks>SWP_NOSIZE</remarks>
                IgnoreResize = 0x0001,

                /// <summary>Retains the current Z order (ignores the hWndInsertAfter parameter).</summary>
                /// <remarks>SWP_NOZORDER</remarks>
                IgnoreZOrder = 0x0004,

                /// <summary>Displays the window.</summary>
                /// <remarks>SWP_SHOWWINDOW</remarks>
                ShowWindow = 0x0040
            }

#endregion

#region Public Methods

            [DllImport("user32.dll")]
            public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);

            //DllImports

            [DllImport("user32.dll")]
            public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

            [DllImport("user32.dll", SetLastError = true)]
            public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool SetWindowPos(IntPtr hWnd,
                                                   IntPtr hWndInsertAfter,
                                                   int x,
                                                   int y,
                                                   int cx,
                                                   int cy,
                                                   uint uFlags);

            [DllImport("dwmapi.dll")]
            public static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref int[] pMargins);

#endregion

#region Private Methods

            [DllImport("user32.dll")]
            private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

#endregion

#region Nested Types

            [StructLayout(LayoutKind.Sequential)]
            public struct WindowArrangement
            {
                private const int HwndTop = 0;

                private const int HwndBottom = 1;

                public const int HwndTopmost = -1;

                private const int HwndNotopmost = -2;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct Rect
            {
                public int Left; // x position of upper-left corner  

                public int Top; // y position of upper-left corner  

                public int Right; // x position of lower-right corner  

                public int Bottom; // y position of lower-right corner  
            }

#endregion
        }

#endregion
    }
}