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 }