public class SdlHost : HwndHost { #region Fields private const string WindowClass = "SdlHost"; private IntPtr _hwndHost; private IntPtr _hWndPrev; private bool _applicationHasFocus; private bool _isPointerCross; private bool _mouseInWindow; private bool _isMouseCaptured; private Point _previousPosition; private readonly IntPtr _pointerArrow; private readonly IntPtr _pointerCross; private readonly HwndMouseState _mouseState = new HwndMouseState(); private Core.View? _core; private readonly int _hostWidth; private readonly int _hostHeight; #endregion #region Events public event EventHandler? HwndLButtonDown; public event EventHandler? HwndLButtonUp; public event EventHandler? HwndLButtonDblClick; public event EventHandler? HwndRButtonDown; public event EventHandler? HwndRButtonUp; public event EventHandler? HwndRButtonDblClick; public event EventHandler? HwndMButtonDown; public event EventHandler? HwndMButtonUp; public event EventHandler? HwndMButtonDblClick; public event EventHandler? HwndX1ButtonDown; public event EventHandler? HwndX1ButtonUp; public event EventHandler? HwndX1ButtonDblClick; public event EventHandler? HwndX2ButtonDown; public event EventHandler? HwndX2ButtonUp; public event EventHandler? HwndX2ButtonDblClick; public event EventHandler? HwndMouseMove; public event EventHandler? HwndMouseEnter; public event EventHandler? HwndMouseLeave; public event EventHandler? HwndMouseWheel; public event EventHandler? HwndKeyDown; public event EventHandler? HwndKeyUp; #endregion #region Construction and Disposal public SdlHost(double width, double height) { _hostWidth = (int)width; _hostHeight = (int)height; if (_hostWidth <= 0 || _hostHeight <= 0) { throw new ArgumentException("_hostWidth <= 0 || _hostHeight <= 0"); } // We must be notified of the application foreground status for our mouse input events Application.Current.Activated += OnApplicationActivated; Application.Current.Deactivated += OnApplicationDeactivated; // Check whether the application is active (it almost certainly is, at this point). if (Application.Current.Windows.Cast().Any(x => x.IsActive)) { _applicationHasFocus = true; } _isPointerCross = false; _pointerArrow = NativeMethods.LoadCursor(IntPtr.Zero, NativeMethods.IDC_ARROW); _pointerCross = NativeMethods.LoadCursor(IntPtr.Zero, NativeMethods.IDC_CROSS); } protected override void Dispose(bool disposing) { // Unhook all events. if (Application.Current != null) { Application.Current.Activated -= OnApplicationActivated; Application.Current.Deactivated -= OnApplicationDeactivated; } base.Dispose(disposing); } #endregion #region Public Methods public new void CaptureMouse() { // Don't do anything if the mouse is already captured if (_isMouseCaptured) { return; } NativeMethods.SetCapture(_hwndHost); _isMouseCaptured = true; } public new void ReleaseMouseCapture() { // Don't do anything if the mouse is not captured if (!_isMouseCaptured) return; NativeMethods.ReleaseCapture(); _isMouseCaptured = false; } public void SetCursorArrow() { _isPointerCross = false; } public void SetCursorCross() { _isPointerCross = true; } public void Update() { if (_core == null) { throw new InvalidOperationException("Core view not initialised"); } if (_isPointerCross) { _core.enableReadCursorValue((int)_previousPosition.X, (int)_previousPosition.Y); } _core.update(); } public void Repaint() { if (_core == null) { throw new InvalidOperationException("Core view not initialised"); } if (_isPointerCross) { _core.enableReadCursorValue((int)_previousPosition.X, (int)_previousPosition.Y); } _core.repaint(); } #endregion #region Graphics Device Control Implementation private void OnApplicationActivated(object? sender, EventArgs e) { _applicationHasFocus = true; } private void OnApplicationDeactivated(object? sender, EventArgs e) { _applicationHasFocus = false; ResetMouseState(); if (_mouseInWindow) { _mouseInWindow = false; RaiseHwndMouseLeave(new HwndMouseEventArgs(_mouseState)); } ReleaseMouseCapture(); } private void ResetMouseState() { // We need to invoke events for any buttons that were pressed bool fireL = _mouseState.LeftButton == MouseButtonState.Pressed; bool fireM = _mouseState.MiddleButton == MouseButtonState.Pressed; bool fireR = _mouseState.RightButton == MouseButtonState.Pressed; bool fireX1 = _mouseState.X1Button == MouseButtonState.Pressed; bool fireX2 = _mouseState.X2Button == MouseButtonState.Pressed; // Update the state of all of the buttons _mouseState.LeftButton = MouseButtonState.Released; _mouseState.MiddleButton = MouseButtonState.Released; _mouseState.RightButton = MouseButtonState.Released; _mouseState.X1Button = MouseButtonState.Released; _mouseState.X2Button = MouseButtonState.Released; // Fire any events var args = new HwndMouseEventArgs(_mouseState); if (fireL) { RaiseHwndLButtonUp(args); } if (fireM) { RaiseHwndMButtonUp(args); } if (fireR) { RaiseHwndRButtonUp(args); } if (fireX1) { RaiseHwndX1ButtonUp(args); } if (fireX2) { RaiseHwndX2ButtonUp(args); } // The mouse is no longer considered to be in our window _mouseInWindow = false; } #endregion #region HWND Management protected override HandleRef BuildWindowCore(HandleRef hwndParent) { // Create the host window as a child of the parent _hwndHost = CreateHostWindow(hwndParent.Handle); _core = new Core.View(); unsafe { _core.initialise(_hwndHost.ToPointer()); } Update(); return new HandleRef(this, _hwndHost); } protected override void DestroyWindowCore(HandleRef hwnd) { _core?.Dispose(); _core = null; NativeMethods.DestroyWindow(hwnd.Handle); _hwndHost = IntPtr.Zero; } private IntPtr CreateHostWindow(IntPtr hWndParent) { // Register our window class RegisterWindowClass(); // Create the window return NativeMethods.CreateWindowEx(0, WindowClass, "", NativeMethods.WS_CHILD | NativeMethods.WS_VISIBLE, 0, 0, _hostWidth, _hostHeight, hWndParent, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); } private void RegisterWindowClass() { var wndClass = new NativeMethods.WNDCLASSEX(); wndClass.cbSize = (uint)Marshal.SizeOf(wndClass); wndClass.hInstance = NativeMethods.GetModuleHandle(null); wndClass.lpfnWndProc = NativeMethods.DefaultWindowProc; wndClass.lpszClassName = WindowClass; wndClass.hCursor = _pointerArrow; NativeMethods.RegisterClassEx(ref wndClass); } #endregion #region WndProc Implementation protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { case NativeMethods.WM_MOUSEWHEEL: if (_mouseInWindow) { int delta = NativeMethods.GetWheelDeltaWParam(wParam.ToInt32()); RaiseHwndMouseWheel(new HwndMouseEventArgs(_mouseState, delta, 0)); } break; case NativeMethods.WM_LBUTTONDOWN: _mouseState.LeftButton = MouseButtonState.Pressed; RaiseHwndLButtonDown(new HwndMouseEventArgs(_mouseState)); break; case NativeMethods.WM_LBUTTONUP: _mouseState.LeftButton = MouseButtonState.Released; RaiseHwndLButtonUp(new HwndMouseEventArgs(_mouseState)); break; case NativeMethods.WM_LBUTTONDBLCLK: RaiseHwndLButtonDblClick(new HwndMouseEventArgs(_mouseState, MouseButton.Left)); break; case NativeMethods.WM_RBUTTONDOWN: _mouseState.RightButton = MouseButtonState.Pressed; RaiseHwndRButtonDown(new HwndMouseEventArgs(_mouseState)); break; case NativeMethods.WM_RBUTTONUP: _mouseState.RightButton = MouseButtonState.Released; RaiseHwndRButtonUp(new HwndMouseEventArgs(_mouseState)); break; case NativeMethods.WM_RBUTTONDBLCLK: RaiseHwndRButtonDblClick(new HwndMouseEventArgs(_mouseState, MouseButton.Right)); break; case NativeMethods.WM_MBUTTONDOWN: _mouseState.MiddleButton = MouseButtonState.Pressed; RaiseHwndMButtonDown(new HwndMouseEventArgs(_mouseState)); break; case NativeMethods.WM_MBUTTONUP: _mouseState.MiddleButton = MouseButtonState.Released; RaiseHwndMButtonUp(new HwndMouseEventArgs(_mouseState)); break; case NativeMethods.WM_MBUTTONDBLCLK: RaiseHwndMButtonDblClick(new HwndMouseEventArgs(_mouseState, MouseButton.Middle)); break; case NativeMethods.WM_XBUTTONDOWN: if (((int)wParam & NativeMethods.MK_XBUTTON1) != 0) { _mouseState.X1Button = MouseButtonState.Pressed; RaiseHwndX1ButtonDown(new HwndMouseEventArgs(_mouseState)); } else if (((int)wParam & NativeMethods.MK_XBUTTON2) != 0) { _mouseState.X2Button = MouseButtonState.Pressed; RaiseHwndX2ButtonDown(new HwndMouseEventArgs(_mouseState)); } break; case NativeMethods.WM_XBUTTONUP: if (((int)wParam & NativeMethods.MK_XBUTTON1) != 0) { _mouseState.X1Button = MouseButtonState.Released; RaiseHwndX1ButtonUp(new HwndMouseEventArgs(_mouseState)); } else if (((int)wParam & NativeMethods.MK_XBUTTON2) != 0) { _mouseState.X2Button = MouseButtonState.Released; RaiseHwndX2ButtonUp(new HwndMouseEventArgs(_mouseState)); } break; case NativeMethods.WM_XBUTTONDBLCLK: if (((int)wParam & NativeMethods.MK_XBUTTON1) != 0) { RaiseHwndX1ButtonDblClick(new HwndMouseEventArgs(_mouseState, MouseButton.XButton1)); } else if (((int)wParam & NativeMethods.MK_XBUTTON2) != 0) { RaiseHwndX2ButtonDblClick(new HwndMouseEventArgs(_mouseState, MouseButton.XButton2)); } break; case NativeMethods.WM_MOUSEMOVE: // If the application isn't in focus, we don't handle this message if (!_applicationHasFocus) { break; } // record the prevous and new position of the mouse _mouseState.ScreenPosition = PointToScreen(new Point( NativeMethods.GetXLParam((int)lParam), NativeMethods.GetYLParam((int)lParam))); _mouseState.WindowPosition = new Point(NativeMethods.GetXLParam((int)lParam), NativeMethods.GetYLParam((int)lParam)); if (!_mouseInWindow) { _mouseInWindow = true; RaiseHwndMouseEnter(new HwndMouseEventArgs(_mouseState)); // Track the previously focused window, and set focus to this window. _hWndPrev = NativeMethods.GetFocus(); NativeMethods.SetFocus(_hwndHost); // send the track mouse event so that we get the WM_MOUSELEAVE message var tme = new NativeMethods.TRACKMOUSEEVENT { cbSize = Marshal.SizeOf(typeof(NativeMethods.TRACKMOUSEEVENT)), dwFlags = NativeMethods.TME_LEAVE, hWnd = hwnd }; NativeMethods.TrackMouseEvent(ref tme); } if (_mouseState.WindowPosition != _previousPosition) { RaiseHwndMouseMove(new HwndMouseEventArgs(_mouseState)); } _previousPosition = _mouseState.WindowPosition; break; case NativeMethods.WM_MOUSELEAVE: // If we have capture, we ignore this message because we're just // going to reset the cursor position back into the window if (_isMouseCaptured) { break; } // Reset the state which releases all buttons and // marks the mouse as not being in the window. ResetMouseState(); RaiseHwndMouseLeave(new HwndMouseEventArgs(_mouseState)); NativeMethods.SetFocus(_hWndPrev); break; case NativeMethods.WM_SETCURSOR: if (_isPointerCross) { NativeMethods.SetCursor(_pointerCross); } else { NativeMethods.SetCursor(_pointerArrow); } handled = true; break; case NativeMethods.WM_KEYDOWN: RaiseHwndKeyDown(new HwndKeyEventArgs(true, (int)wParam, (long)lParam)); break; case NativeMethods.WM_KEYUP: RaiseHwndKeyUp(new HwndKeyEventArgs(false, (int)wParam, (long)lParam)); break; } return base.WndProc(hwnd, msg, wParam, lParam, ref handled); } protected virtual void RaiseHwndLButtonDown(HwndMouseEventArgs args) { var handler = HwndLButtonDown; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndLButtonUp(HwndMouseEventArgs args) { var handler = HwndLButtonUp; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndRButtonDown(HwndMouseEventArgs args) { var handler = HwndRButtonDown; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndRButtonUp(HwndMouseEventArgs args) { var handler = HwndRButtonUp; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndMButtonDown(HwndMouseEventArgs args) { var handler = HwndMButtonDown; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndMButtonUp(HwndMouseEventArgs args) { var handler = HwndMButtonUp; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndLButtonDblClick(HwndMouseEventArgs args) { var handler = HwndLButtonDblClick; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndRButtonDblClick(HwndMouseEventArgs args) { var handler = HwndRButtonDblClick; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndMButtonDblClick(HwndMouseEventArgs args) { var handler = HwndMButtonDblClick; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndMouseEnter(HwndMouseEventArgs args) { var handler = HwndMouseEnter; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndX1ButtonDown(HwndMouseEventArgs args) { var handler = HwndX1ButtonDown; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndX1ButtonUp(HwndMouseEventArgs args) { var handler = HwndX1ButtonUp; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndX2ButtonDown(HwndMouseEventArgs args) { var handler = HwndX2ButtonDown; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndX2ButtonUp(HwndMouseEventArgs args) { var handler = HwndX2ButtonUp; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndX1ButtonDblClick(HwndMouseEventArgs args) { var handler = HwndX1ButtonDblClick; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndX2ButtonDblClick(HwndMouseEventArgs args) { var handler = HwndX2ButtonDblClick; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndMouseLeave(HwndMouseEventArgs args) { var handler = HwndMouseLeave; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndMouseMove(HwndMouseEventArgs args) { var handler = HwndMouseMove; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndMouseWheel(HwndMouseEventArgs args) { var handler = HwndMouseWheel; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndKeyDown(HwndKeyEventArgs args) { var handler = HwndKeyDown; if (handler != null) { handler(this, args); } } protected virtual void RaiseHwndKeyUp(HwndKeyEventArgs args) { var handler = HwndKeyUp; if (handler != null) { handler(this, args); } } #endregion }