github.com/rajveermalviya/gamen@v0.1.2-0.20220930195403-9be15877c1aa/internal/win32/window.go (about)

     1  //go:build windows
     2  
     3  package win32
     4  
     5  import (
     6  	"math"
     7  	"sync"
     8  	"unsafe"
     9  
    10  	"github.com/rajveermalviya/gamen/cursors"
    11  	"github.com/rajveermalviya/gamen/dpi"
    12  	"github.com/rajveermalviya/gamen/events"
    13  	"github.com/rajveermalviya/gamen/internal/common/atomicx"
    14  	"github.com/rajveermalviya/gamen/internal/common/mathx"
    15  	"github.com/rajveermalviya/gamen/internal/win32/procs"
    16  	"golang.org/x/sys/windows"
    17  )
    18  
    19  type Window struct {
    20  	d *Display
    21  	// window handle
    22  	hwnd uintptr
    23  	// we allow destroy function to be called multiple
    24  	// times, but in reality we run it once
    25  	destroyOnce sync.Once
    26  	mu          sync.Mutex
    27  
    28  	minSize dpi.PhysicalSize[uint32] // shared mutex
    29  	maxSize dpi.PhysicalSize[uint32] // shared mutex
    30  	wpPrev  procs.WINDOWPLACEMENT    // shared mutex
    31  
    32  	currentCursor atomicx.Uint[cursors.Icon] // shared atomic
    33  	maximized     atomicx.Bool               // shared atomic
    34  
    35  	cursorIsOutside    bool                        // non-shared
    36  	cursorPos          dpi.PhysicalPosition[int16] // non-shared
    37  	cursorCaptureCount int                         // non-shared
    38  	modifiers          events.ModifiersState       // non-shared
    39  
    40  	// callbacks
    41  	resizedCb           atomicx.Pointer[events.WindowResizedCallback]
    42  	closeRequestedCb    atomicx.Pointer[events.WindowCloseRequestedCallback]
    43  	focusedCb           atomicx.Pointer[events.WindowFocusedCallback]
    44  	unfocusedCb         atomicx.Pointer[events.WindowUnfocusedCallback]
    45  	cursorEnteredCb     atomicx.Pointer[events.WindowCursorEnteredCallback]
    46  	cursorLeftCb        atomicx.Pointer[events.WindowCursorLeftCallback]
    47  	cursorMovedCb       atomicx.Pointer[events.WindowCursorMovedCallback]
    48  	mouseWheelCb        atomicx.Pointer[events.WindowMouseScrollCallback]
    49  	mouseInputCb        atomicx.Pointer[events.WindowMouseInputCallback]
    50  	modifiersChangedCb  atomicx.Pointer[events.WindowModifiersChangedCallback]
    51  	keyboardInputCb     atomicx.Pointer[events.WindowKeyboardInputCallback]
    52  	receivedCharacterCb atomicx.Pointer[events.WindowReceivedCharacterCallback]
    53  }
    54  
    55  var windowClassName = must(windows.UTF16PtrFromString("Window Class"))
    56  
    57  const decoratedWindowStyles = procs.WS_OVERLAPPED |
    58  	procs.WS_SYSMENU |
    59  	procs.WS_CAPTION |
    60  	procs.WS_SIZEBOX
    61  
    62  const decoratedWindowExStyles = procs.WS_EX_WINDOWEDGE
    63  
    64  const defaultStyles = procs.WS_VISIBLE | // visible
    65  	procs.WS_CLIPSIBLINGS | // clip window behind
    66  	procs.WS_CLIPCHILDREN | // clip window behind
    67  	procs.WS_MAXIMIZEBOX |
    68  	procs.WS_MINIMIZEBOX |
    69  	decoratedWindowStyles
    70  
    71  const defaultExStyles = procs.WS_EX_LEFT |
    72  	procs.WS_EX_APPWINDOW |
    73  	decoratedWindowExStyles
    74  
    75  func NewWindow(d *Display) (*Window, error) {
    76  	class := procs.WNDCLASSEXW{
    77  		CbSize:        uint32(unsafe.Sizeof(procs.WNDCLASSEXW{})),
    78  		Style:         procs.CS_HREDRAW | procs.CS_VREDRAW,
    79  		LpfnWndProc:   windowProcCb,
    80  		CbClsExtra:    0,
    81  		CbWndExtra:    0,
    82  		HInstance:     procs.GetModuleHandleW(),
    83  		HIcon:         0,
    84  		HCursor:       0,
    85  		HbrBackground: 0,
    86  		LpszMenuName:  nil,
    87  		LpszClassName: windowClassName,
    88  		HIconSm:       0,
    89  	}
    90  	procs.RegisterClassExW(uintptr(unsafe.Pointer(&class)))
    91  
    92  	w := &Window{
    93  		minSize: dpi.PhysicalSize[uint32]{},
    94  		maxSize: dpi.PhysicalSize[uint32]{
    95  			Width:  math.MaxInt16,
    96  			Height: math.MaxInt16,
    97  		},
    98  	}
    99  	w.currentCursor.Store(cursors.Default)
   100  
   101  	hwnd := procs.CreateWindowExW(
   102  		defaultExStyles,
   103  		uintptr(unsafe.Pointer(windowClassName)),
   104  		0,
   105  		defaultStyles,
   106  		procs.CW_USEDEFAULT,
   107  		procs.CW_USEDEFAULT,
   108  		procs.CW_USEDEFAULT,
   109  		procs.CW_USEDEFAULT,
   110  		0,
   111  		0,
   112  		procs.GetModuleHandleW(),
   113  		uintptr(unsafe.Pointer(w)),
   114  	)
   115  
   116  	if hwnd == 0 || w.hwnd == 0 {
   117  		return nil, windows.GetLastError()
   118  	}
   119  
   120  	d.windows[hwnd] = w
   121  
   122  	return w, nil
   123  }
   124  
   125  func (w *Window) Win32Hinstance() uintptr { return procs.GetModuleHandleW() }
   126  func (w *Window) Win32Hwnd() uintptr      { return w.hwnd }
   127  
   128  func (w *Window) Destroy() {
   129  	w.destroyOnce.Do(func() {
   130  		w.resizedCb.Store(nil)
   131  		w.closeRequestedCb.Store(nil)
   132  		w.focusedCb.Store(nil)
   133  		w.unfocusedCb.Store(nil)
   134  		w.cursorEnteredCb.Store(nil)
   135  		w.cursorLeftCb.Store(nil)
   136  		w.cursorMovedCb.Store(nil)
   137  		w.mouseWheelCb.Store(nil)
   138  		w.mouseInputCb.Store(nil)
   139  		w.modifiersChangedCb.Store(nil)
   140  		w.keyboardInputCb.Store(nil)
   141  		w.receivedCharacterCb.Store(nil)
   142  
   143  		procs.DestroyWindow(w.hwnd)
   144  	})
   145  }
   146  
   147  func (w *Window) SetTitle(title string) {
   148  	titlePtr := windows.StringToUTF16Ptr(title)
   149  	procs.SetWindowTextW(w.hwnd, uintptr(unsafe.Pointer(titlePtr)))
   150  }
   151  
   152  func (w *Window) InnerSize() dpi.PhysicalSize[uint32] {
   153  	var rect procs.RECT
   154  	if !procs.GetClientRect(w.hwnd, uintptr(unsafe.Pointer(&rect))) {
   155  		panic("GetClientRect failed")
   156  	}
   157  	return dpi.PhysicalSize[uint32]{
   158  		Width:  uint32(rect.Right),
   159  		Height: uint32(rect.Bottom),
   160  	}
   161  }
   162  
   163  func (w *Window) SetInnerSize(size dpi.Size[uint32]) {
   164  	physicalSize := size.ToPhysical(1)
   165  
   166  	rect := &procs.RECT{
   167  		Top:    0,
   168  		Left:   0,
   169  		Bottom: int32(physicalSize.Height),
   170  		Right:  int32(physicalSize.Width),
   171  	}
   172  
   173  	style := procs.GetWindowLong(w.hwnd, procs.GWL_STYLE)
   174  	styleEx := procs.GetWindowLong(w.hwnd, procs.GWL_EXSTYLE)
   175  	if !procs.AdjustWindowRectEx(w.hwnd, style, styleEx, uintptr(unsafe.Pointer(rect))) {
   176  		panic("AdjustWindowRectEx failed")
   177  	}
   178  
   179  	outerX := mathx.Abs(rect.Right - rect.Left)
   180  	outerY := mathx.Abs(rect.Top - rect.Bottom)
   181  
   182  	procs.SetWindowPos(
   183  		w.hwnd,
   184  		0,
   185  		0, 0,
   186  		uintptr(outerX),
   187  		uintptr(outerY),
   188  		procs.SWP_ASYNCWINDOWPOS|
   189  			procs.SWP_NOZORDER|
   190  			procs.SWP_NOREPOSITION|
   191  			procs.SWP_NOMOVE|
   192  			procs.SWP_NOACTIVATE,
   193  	)
   194  
   195  	procs.InvalidateRgn(w.hwnd, 0, 0)
   196  }
   197  
   198  func (w *Window) SetMinInnerSize(size dpi.Size[uint32]) {
   199  	w.mu.Lock()
   200  	defer w.mu.Unlock()
   201  	w.minSize = size.ToPhysical(1)
   202  }
   203  
   204  func (w *Window) SetMaxInnerSize(size dpi.Size[uint32]) {
   205  	w.mu.Lock()
   206  	defer w.mu.Unlock()
   207  	w.maxSize = size.ToPhysical(1)
   208  }
   209  
   210  func (w *Window) Maximized() bool {
   211  	return w.maximized.Load()
   212  }
   213  
   214  func (w *Window) SetMinimized() {
   215  	procs.ShowWindow(w.hwnd, procs.SW_MINIMIZE)
   216  }
   217  
   218  func (w *Window) SetMaximized(maximized bool) {
   219  	if maximized {
   220  		procs.ShowWindow(w.hwnd, procs.SW_MAXIMIZE)
   221  	} else {
   222  		procs.ShowWindow(w.hwnd, procs.SW_RESTORE)
   223  	}
   224  }
   225  
   226  func (w *Window) SetCursorIcon(icon cursors.Icon) {
   227  	w.currentCursor.Store(icon)
   228  	procs.SetCursor(procs.LoadCursorW(0, toWin32Cursor(icon)))
   229  }
   230  
   231  func (w *Window) SetCursorVisible(visible bool) {
   232  	if visible {
   233  		procs.ShowCursor(1)
   234  	} else {
   235  		procs.ShowCursor(0)
   236  	}
   237  }
   238  
   239  func (w *Window) SetFullscreen(fullscreen bool) {
   240  	dwStyle := procs.GetWindowLong(w.hwnd, procs.GWL_STYLE)
   241  	dwExStyle := procs.GetWindowLong(w.hwnd, procs.GWL_EXSTYLE)
   242  
   243  	if fullscreen {
   244  		mi := procs.MONITORINFO{
   245  			CbSize: uint32(unsafe.Sizeof(procs.MONITORINFO{})),
   246  		}
   247  		monitor := procs.MonitorFromWindow(w.hwnd, procs.MONITOR_DEFAULTTOPRIMARY)
   248  
   249  		var wp procs.WINDOWPLACEMENT
   250  		if procs.GetWindowPlacement(w.hwnd, uintptr(unsafe.Pointer(&wp))) &&
   251  			procs.GetMonitorInfoW(monitor, uintptr(unsafe.Pointer(&mi))) {
   252  			w.mu.Lock()
   253  			w.wpPrev = wp
   254  			w.mu.Unlock()
   255  
   256  			procs.SetWindowLong(w.hwnd, procs.GWL_STYLE, dwStyle&^decoratedWindowStyles)
   257  			procs.SetWindowLong(w.hwnd, procs.GWL_EXSTYLE, dwExStyle&^decoratedWindowExStyles)
   258  			procs.SetWindowPos(
   259  				w.hwnd, procs.HWND_TOP,
   260  				uintptr(mi.RcMonitor.Left), uintptr(mi.RcMonitor.Top),
   261  				uintptr(mi.RcMonitor.Right-mi.RcMonitor.Left),
   262  				uintptr(mi.RcMonitor.Bottom-mi.RcMonitor.Top),
   263  				procs.SWP_NOOWNERZORDER|procs.SWP_FRAMECHANGED,
   264  			)
   265  		}
   266  	} else {
   267  		w.mu.Lock()
   268  		wp := w.wpPrev
   269  		w.mu.Unlock()
   270  
   271  		procs.SetWindowLong(w.hwnd, procs.GWL_STYLE, dwStyle|decoratedWindowStyles)
   272  		procs.SetWindowLong(w.hwnd, procs.GWL_EXSTYLE, dwExStyle|decoratedWindowExStyles)
   273  		procs.SetWindowPlacement(w.hwnd, uintptr(unsafe.Pointer(&wp)))
   274  		procs.SetWindowPos(
   275  			w.hwnd, 0,
   276  			0, 0,
   277  			0, 0,
   278  			procs.SWP_NOMOVE|
   279  				procs.SWP_NOSIZE|
   280  				procs.SWP_NOZORDER|
   281  				procs.SWP_NOOWNERZORDER|
   282  				procs.SWP_FRAMECHANGED,
   283  		)
   284  	}
   285  }
   286  
   287  func (w *Window) Fullscreen() bool {
   288  	windowSize := w.InnerSize()
   289  
   290  	monitor := procs.MonitorFromWindow(w.hwnd, procs.MONITOR_DEFAULTTOPRIMARY)
   291  	mi := procs.MONITORINFO{CbSize: uint32(unsafe.Sizeof(procs.MONITORINFO{}))}
   292  	procs.GetMonitorInfoW(monitor, uintptr(unsafe.Pointer(&mi)))
   293  
   294  	if int32(windowSize.Width) != mathx.Abs(mi.RcMonitor.Right-mi.RcMonitor.Left) ||
   295  		int32(windowSize.Height) != mathx.Abs(mi.RcMonitor.Bottom-mi.RcMonitor.Top) {
   296  		return false
   297  	}
   298  	if w.Decorated() {
   299  		return false
   300  	}
   301  	return true
   302  }
   303  
   304  func (w *Window) DragWindow() {
   305  	var pos procs.POINT
   306  	procs.GetCursorPos(uintptr(unsafe.Pointer(&pos)))
   307  
   308  	points := procs.POINTS{
   309  		X: int16(pos.X),
   310  		Y: int16(pos.Y),
   311  	}
   312  	procs.ReleaseCapture()
   313  	procs.PostMessageW(
   314  		w.hwnd,
   315  		procs.WM_NCLBUTTONDOWN,
   316  		procs.HTCAPTION,
   317  		uintptr(unsafe.Pointer(&points)),
   318  	)
   319  }
   320  
   321  func (w *Window) SetDecorations(decorate bool) {
   322  	dwStyle := procs.GetWindowLong(w.hwnd, procs.GWL_STYLE)
   323  	dwExStyle := procs.GetWindowLong(w.hwnd, procs.GWL_EXSTYLE)
   324  
   325  	if decorate {
   326  		dwStyle |= decoratedWindowStyles
   327  		dwExStyle |= decoratedWindowExStyles
   328  	} else {
   329  		dwStyle &^= decoratedWindowStyles
   330  		dwExStyle &^= decoratedWindowExStyles
   331  	}
   332  
   333  	procs.SetWindowLong(w.hwnd, procs.GWL_STYLE, dwStyle)
   334  	procs.SetWindowLong(w.hwnd, procs.GWL_EXSTYLE, dwExStyle)
   335  	procs.SetWindowPos(
   336  		w.hwnd, 0,
   337  		0, 0,
   338  		0, 0,
   339  		procs.SWP_NOMOVE|
   340  			procs.SWP_NOSIZE|
   341  			procs.SWP_NOZORDER|
   342  			procs.SWP_NOOWNERZORDER|
   343  			procs.SWP_FRAMECHANGED,
   344  	)
   345  }
   346  
   347  func (w *Window) Decorated() bool {
   348  	dwStyle := procs.GetWindowLong(w.hwnd, procs.GWL_STYLE)
   349  	dwExStyle := procs.GetWindowLong(w.hwnd, procs.GWL_EXSTYLE)
   350  
   351  	if dwStyle&decoratedWindowStyles != 0 &&
   352  		dwExStyle&decoratedWindowExStyles != 0 {
   353  		return true
   354  	}
   355  	return false
   356  }
   357  
   358  func (w *Window) SetCloseRequestedCallback(cb events.WindowCloseRequestedCallback) {
   359  	w.closeRequestedCb.Store(&cb)
   360  }
   361  func (w *Window) SetResizedCallback(cb events.WindowResizedCallback) {
   362  	w.resizedCb.Store(&cb)
   363  }
   364  func (w *Window) SetFocusedCallback(cb events.WindowFocusedCallback) {
   365  	w.focusedCb.Store(&cb)
   366  }
   367  func (w *Window) SetUnfocusedCallback(cb events.WindowUnfocusedCallback) {
   368  	w.unfocusedCb.Store(&cb)
   369  }
   370  func (w *Window) SetCursorEnteredCallback(cb events.WindowCursorEnteredCallback) {
   371  	w.cursorEnteredCb.Store(&cb)
   372  }
   373  func (w *Window) SetCursorLeftCallback(cb events.WindowCursorLeftCallback) {
   374  	w.cursorLeftCb.Store(&cb)
   375  }
   376  func (w *Window) SetCursorMovedCallback(cb events.WindowCursorMovedCallback) {
   377  	w.cursorMovedCb.Store(&cb)
   378  }
   379  func (w *Window) SetMouseScrollCallback(cb events.WindowMouseScrollCallback) {
   380  	w.mouseWheelCb.Store(&cb)
   381  }
   382  func (w *Window) SetMouseInputCallback(cb events.WindowMouseInputCallback) {
   383  	w.mouseInputCb.Store(&cb)
   384  }
   385  func (w *Window) SetTouchInputCallback(cb events.WindowTouchInputCallback) {
   386  	// TODO:
   387  }
   388  func (w *Window) SetModifiersChangedCallback(cb events.WindowModifiersChangedCallback) {
   389  	w.modifiersChangedCb.Store(&cb)
   390  }
   391  func (w *Window) SetKeyboardInputCallback(cb events.WindowKeyboardInputCallback) {
   392  	w.keyboardInputCb.Store(&cb)
   393  }
   394  func (w *Window) SetReceivedCharacterCallback(cb events.WindowReceivedCharacterCallback) {
   395  	w.receivedCharacterCb.Store(&cb)
   396  }
   397  
   398  var windowProcCb = windows.NewCallback(windowProc)
   399  
   400  func windowProc(window, msg, wparam, lparam uintptr) uintptr {
   401  	userData := procs.GetWindowLong(window, procs.GWL_USERDATA)
   402  
   403  	if userData == 0 {
   404  		if msg == procs.WM_NCCREATE {
   405  			createStruct := (*procs.CREATESTRUCTW)(unsafe.Pointer(lparam))
   406  			w := (*Window)(unsafe.Pointer(createStruct.LpCreateParams))
   407  			w.hwnd = window
   408  			procs.SetWindowLong(window, procs.GWL_USERDATA, uintptr(unsafe.Pointer(w)))
   409  		}
   410  		return procs.DefWindowProcW(window, msg, wparam, lparam)
   411  	}
   412  
   413  	w := (*Window)(unsafe.Pointer(userData))
   414  
   415  	switch msg {
   416  	case procs.WM_CLOSE:
   417  
   418  		if cb := w.closeRequestedCb.Load(); cb != nil {
   419  			if cb := (*cb); cb != nil {
   420  				cb()
   421  			}
   422  		}
   423  		return 0
   424  
   425  	case procs.WM_SIZE:
   426  		size := dpi.PhysicalSize[uint32]{
   427  			Width:  uint32(loword(uint32(lparam))),
   428  			Height: uint32(hiword(uint32(lparam))),
   429  		}
   430  
   431  		if wparam == procs.SIZE_MAXIMIZED {
   432  			w.maximized.Store(true)
   433  		} else {
   434  			w.maximized.Store(false)
   435  		}
   436  
   437  		if size.Width != 0 && size.Height != 0 {
   438  			if cb := w.resizedCb.Load(); cb != nil {
   439  				if cb := (*cb); cb != nil {
   440  					cb(size.Width, size.Height, 1)
   441  				}
   442  			}
   443  		}
   444  		return 0
   445  
   446  	case procs.WM_GETMINMAXINFO:
   447  		mmi := (*procs.MINMAXINFO)(unsafe.Pointer(lparam))
   448  
   449  		w.mu.Lock()
   450  		minSize := w.minSize
   451  		w.mu.Unlock()
   452  
   453  		if minSize.Width != 0 && minSize.Height != 0 {
   454  			rect := &procs.RECT{
   455  				Top:    0,
   456  				Left:   0,
   457  				Bottom: int32(minSize.Height),
   458  				Right:  int32(minSize.Width),
   459  			}
   460  
   461  			style := procs.GetWindowLong(w.hwnd, procs.GWL_STYLE)
   462  			styleEx := procs.GetWindowLong(w.hwnd, procs.GWL_EXSTYLE)
   463  			if !procs.AdjustWindowRectEx(w.hwnd, style, styleEx, uintptr(unsafe.Pointer(rect))) {
   464  				panic("AdjustWindowRectEx failed")
   465  			}
   466  
   467  			mmi.PtMinTrackSize = procs.POINT{
   468  				X: rect.Right - rect.Left,
   469  				Y: rect.Bottom - rect.Top,
   470  			}
   471  		}
   472  
   473  		w.mu.Lock()
   474  		maxSize := w.maxSize
   475  		w.mu.Unlock()
   476  
   477  		if maxSize.Width != 0 && maxSize.Height != 0 {
   478  			rect := &procs.RECT{
   479  				Top:    0,
   480  				Left:   0,
   481  				Bottom: int32(maxSize.Height),
   482  				Right:  int32(maxSize.Width),
   483  			}
   484  
   485  			style := procs.GetWindowLong(w.hwnd, procs.GWL_STYLE)
   486  			styleEx := procs.GetWindowLong(w.hwnd, procs.GWL_EXSTYLE)
   487  			if !procs.AdjustWindowRectEx(w.hwnd, style, styleEx, uintptr(unsafe.Pointer(rect))) {
   488  				panic("AdjustWindowRectEx failed")
   489  			}
   490  
   491  			mmi.PtMaxTrackSize = procs.POINT{
   492  				X: rect.Right - rect.Left,
   493  				Y: rect.Bottom - rect.Top,
   494  			}
   495  		}
   496  
   497  		return 0
   498  
   499  	case procs.WM_SETCURSOR:
   500  		if loword(uint32(lparam)) == procs.HTCLIENT {
   501  			procs.SetCursor(procs.LoadCursorW(0, toWin32Cursor(w.currentCursor.Load())))
   502  		}
   503  
   504  	case procs.WM_MOUSEMOVE:
   505  		if w.cursorIsOutside {
   506  			w.cursorIsOutside = false
   507  
   508  			if cb := w.cursorEnteredCb.Load(); cb != nil {
   509  				if cb := (*cb); cb != nil {
   510  					cb()
   511  				}
   512  			}
   513  
   514  			procs.TrackMouseEvent(uintptr(unsafe.Pointer(&procs.TRACKMOUSEEVENT{
   515  				CbSize:      uint32(unsafe.Sizeof(procs.TRACKMOUSEEVENT{})),
   516  				DwFlags:     procs.TME_LEAVE,
   517  				HwndTrack:   window,
   518  				DwHoverTime: procs.HOVER_DEFAULT,
   519  			})))
   520  		}
   521  
   522  		pos := dpi.PhysicalPosition[int16]{
   523  			X: int16(loword(uint32(lparam))),
   524  			Y: int16(hiword(uint32(lparam))),
   525  		}
   526  
   527  		if w.cursorPos != pos {
   528  			w.cursorPos = pos
   529  
   530  			if cb := w.cursorMovedCb.Load(); cb != nil {
   531  				if cb := (*cb); cb != nil {
   532  					cb(float64(pos.X), float64(pos.Y))
   533  				}
   534  			}
   535  		}
   536  		return 0
   537  
   538  	case procs.WM_MOUSELEAVE:
   539  		w.cursorIsOutside = true
   540  
   541  		if cb := w.cursorLeftCb.Load(); cb != nil {
   542  			if cb := (*cb); cb != nil {
   543  				cb()
   544  			}
   545  		}
   546  		return 0
   547  
   548  	case procs.WM_MOUSEWHEEL:
   549  		value := float64(int16(wparam>>16)) / procs.WHEEL_DELTA
   550  
   551  		if cb := w.mouseWheelCb.Load(); cb != nil {
   552  			if cb := (*cb); cb != nil {
   553  				cb(
   554  					events.MouseScrollDeltaLine,
   555  					events.MouseScrollAxisVertical,
   556  					value,
   557  				)
   558  			}
   559  		}
   560  		return 0
   561  
   562  	case procs.WM_MOUSEHWHEEL:
   563  		value := -float64(int16(wparam>>16)) / procs.WHEEL_DELTA
   564  
   565  		if cb := w.mouseWheelCb.Load(); cb != nil {
   566  			if cb := (*cb); cb != nil {
   567  				cb(
   568  					events.MouseScrollDeltaLine,
   569  					events.MouseScrollAxisHorizontal,
   570  					value,
   571  				)
   572  			}
   573  		}
   574  		return 0
   575  
   576  	case procs.WM_LBUTTONDOWN:
   577  		procs.SetCapture(window)
   578  		w.cursorCaptureCount++
   579  
   580  		if cb := w.mouseInputCb.Load(); cb != nil {
   581  			if cb := (*cb); cb != nil {
   582  				cb(
   583  					events.ButtonStatePressed,
   584  					events.MouseButtonLeft,
   585  				)
   586  			}
   587  		}
   588  		return 0
   589  
   590  	case procs.WM_LBUTTONUP:
   591  		w.cursorCaptureCount = mathx.Max(0, w.cursorCaptureCount-1)
   592  		if w.cursorCaptureCount == 0 {
   593  			procs.ReleaseCapture()
   594  		}
   595  
   596  		if cb := w.mouseInputCb.Load(); cb != nil {
   597  			if cb := (*cb); cb != nil {
   598  				cb(
   599  					events.ButtonStateReleased,
   600  					events.MouseButtonLeft,
   601  				)
   602  			}
   603  		}
   604  		return 0
   605  
   606  	case procs.WM_RBUTTONDOWN:
   607  		procs.SetCapture(window)
   608  		w.cursorCaptureCount++
   609  
   610  		if cb := w.mouseInputCb.Load(); cb != nil {
   611  			if cb := (*cb); cb != nil {
   612  				cb(
   613  					events.ButtonStatePressed,
   614  					events.MouseButtonRight,
   615  				)
   616  			}
   617  		}
   618  		return 0
   619  
   620  	case procs.WM_RBUTTONUP:
   621  		w.cursorCaptureCount = mathx.Max(0, w.cursorCaptureCount-1)
   622  		if w.cursorCaptureCount == 0 {
   623  			procs.ReleaseCapture()
   624  		}
   625  
   626  		if cb := w.mouseInputCb.Load(); cb != nil {
   627  			if cb := (*cb); cb != nil {
   628  				cb(
   629  					events.ButtonStateReleased,
   630  					events.MouseButtonRight,
   631  				)
   632  			}
   633  		}
   634  		return 0
   635  
   636  	case procs.WM_MBUTTONDOWN:
   637  		procs.SetCapture(window)
   638  		w.cursorCaptureCount++
   639  
   640  		if cb := w.mouseInputCb.Load(); cb != nil {
   641  			if cb := (*cb); cb != nil {
   642  				cb(
   643  					events.ButtonStatePressed,
   644  					events.MouseButtonMiddle,
   645  				)
   646  			}
   647  		}
   648  		return 0
   649  
   650  	case procs.WM_MBUTTONUP:
   651  		w.cursorCaptureCount = mathx.Max(0, w.cursorCaptureCount-1)
   652  		if w.cursorCaptureCount == 0 {
   653  			procs.ReleaseCapture()
   654  		}
   655  
   656  		if cb := w.mouseInputCb.Load(); cb != nil {
   657  			if cb := (*cb); cb != nil {
   658  				cb(
   659  					events.ButtonStateReleased,
   660  					events.MouseButtonMiddle,
   661  				)
   662  			}
   663  		}
   664  		return 0
   665  
   666  	case procs.WM_XBUTTONDOWN:
   667  		procs.SetCapture(window)
   668  		w.cursorCaptureCount++
   669  
   670  		if cb := w.mouseInputCb.Load(); cb != nil {
   671  			if cb := (*cb); cb != nil {
   672  				cb(
   673  					events.ButtonStatePressed,
   674  					events.MouseButton(loword(uint32(wparam))),
   675  				)
   676  			}
   677  		}
   678  		return 0
   679  
   680  	case procs.WM_XBUTTONUP:
   681  		w.cursorCaptureCount = mathx.Max(0, w.cursorCaptureCount-1)
   682  		if w.cursorCaptureCount == 0 {
   683  			procs.ReleaseCapture()
   684  		}
   685  
   686  		if cb := w.mouseInputCb.Load(); cb != nil {
   687  			if cb := (*cb); cb != nil {
   688  				cb(
   689  					events.ButtonStateReleased,
   690  					events.MouseButton(loword(uint32(wparam))),
   691  				)
   692  			}
   693  		}
   694  		return 0
   695  
   696  	case procs.WM_CAPTURECHANGED:
   697  		if lparam != window {
   698  			w.cursorCaptureCount = 0
   699  		}
   700  		return 0
   701  
   702  	case procs.WM_SETFOCUS:
   703  		if cb := w.focusedCb.Load(); cb != nil {
   704  			if cb := (*cb); cb != nil {
   705  				cb()
   706  			}
   707  		}
   708  
   709  		if m := getModifiersState(); m != 0 {
   710  			w.modifiers = m
   711  
   712  			if cb := w.modifiersChangedCb.Load(); cb != nil {
   713  				if cb := (*cb); cb != nil {
   714  					cb(m)
   715  				}
   716  			}
   717  		}
   718  		return 0
   719  
   720  	case procs.WM_KILLFOCUS:
   721  		if w.modifiers != 0 {
   722  			w.modifiers = 0
   723  
   724  			if cb := w.modifiersChangedCb.Load(); cb != nil {
   725  				if cb := (*cb); cb != nil {
   726  					cb(0)
   727  				}
   728  			}
   729  		}
   730  
   731  		if cb := w.unfocusedCb.Load(); cb != nil {
   732  			if cb := (*cb); cb != nil {
   733  				cb()
   734  			}
   735  		}
   736  		return 0
   737  
   738  	case procs.WM_KEYDOWN, procs.WM_SYSKEYDOWN:
   739  		if msg == procs.WM_SYSKEYDOWN && wparam == procs.VK_F4 {
   740  			return procs.DefWindowProcW(window, msg, wparam, lparam)
   741  		}
   742  
   743  		if cb := w.keyboardInputCb.Load(); cb != nil {
   744  			if cb := (*cb); cb != nil {
   745  				scancode := ((lparam >> 16) & 0xff)
   746  				extended := (lparam & 0x01000000) != 0
   747  				if extended {
   748  					scancode |= 0xE000
   749  				} else {
   750  					scancode |= 0x0000
   751  				}
   752  
   753  				cb(
   754  					events.ButtonStatePressed,
   755  					events.ScanCode(scancode),
   756  					mapVK(wparam, scancode, extended),
   757  				)
   758  			}
   759  		}
   760  
   761  		m := getModifiersState()
   762  		if w.modifiers != m {
   763  			w.modifiers = m
   764  
   765  			if cb := w.modifiersChangedCb.Load(); cb != nil {
   766  				if cb := (*cb); cb != nil {
   767  					cb(m)
   768  				}
   769  			}
   770  		}
   771  
   772  		if cb := w.receivedCharacterCb.Load(); cb != nil {
   773  			if cb := (*cb); cb != nil {
   774  				// win32 doesn't send WM_CHAR message for delete key
   775  				if wparam == procs.VK_DELETE {
   776  					cb('\u007F')
   777  				}
   778  			}
   779  		}
   780  		return 0
   781  
   782  	case procs.WM_KEYUP, procs.WM_SYSKEYUP:
   783  		if cb := w.keyboardInputCb.Load(); cb != nil {
   784  			if cb := (*cb); cb != nil {
   785  				scancode := ((lparam >> 16) & 0xff)
   786  				extended := (lparam & 0x01000000) != 0
   787  				if extended {
   788  					scancode |= 0xE000
   789  				} else {
   790  					scancode |= 0x0000
   791  				}
   792  
   793  				cb(
   794  					events.ButtonStateReleased,
   795  					events.ScanCode(scancode),
   796  					mapVK(wparam, scancode, extended),
   797  				)
   798  			}
   799  		}
   800  
   801  		m := getModifiersState()
   802  		if w.modifiers != m {
   803  			w.modifiers = m
   804  
   805  			if cb := w.modifiersChangedCb.Load(); cb != nil {
   806  				if cb := (*cb); cb != nil {
   807  					cb(m)
   808  				}
   809  			}
   810  		}
   811  		return 0
   812  
   813  	case procs.WM_CHAR, procs.WM_SYSCHAR:
   814  		if cb := w.receivedCharacterCb.Load(); cb != nil {
   815  			if cb := (*cb); cb != nil {
   816  				cb(decodeUtf16(uint16(wparam)))
   817  			}
   818  		}
   819  		return 0
   820  	}
   821  
   822  	return procs.DefWindowProcW(window, msg, wparam, lparam)
   823  }
   824  
   825  func getModifiersState() (m events.ModifiersState) {
   826  	var state [256]byte
   827  	procs.GetKeyboardState(uintptr(unsafe.Pointer(&state)))
   828  
   829  	for i, v := range state {
   830  		if v&(1<<7) != 0 { // if pressed
   831  			switch i {
   832  			case procs.VK_SHIFT:
   833  				m |= events.ModifiersStateShift
   834  			case procs.VK_CONTROL:
   835  				m |= events.ModifiersStateCtrl
   836  			case procs.VK_MENU:
   837  				m |= events.ModifiersStateAlt
   838  			case procs.VK_LWIN, procs.VK_RWIN:
   839  				m |= events.ModifiersStateLogo
   840  			}
   841  		}
   842  	}
   843  
   844  	return
   845  }