github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/wm/os_windows.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package wm 4 5 import ( 6 "errors" 7 "fmt" 8 "image" 9 "reflect" 10 "runtime" 11 "sort" 12 "strings" 13 "sync" 14 "time" 15 "unicode" 16 "unsafe" 17 18 syscall "golang.org/x/sys/windows" 19 20 "github.com/cybriq/giocore/app/internal/windows" 21 "github.com/cybriq/giocore/unit" 22 gowindows "golang.org/x/sys/windows" 23 24 "github.com/cybriq/giocore/f32" 25 "github.com/cybriq/giocore/io/clipboard" 26 "github.com/cybriq/giocore/io/key" 27 "github.com/cybriq/giocore/io/pointer" 28 "github.com/cybriq/giocore/io/system" 29 ) 30 31 type ViewEvent struct { 32 HWND uintptr 33 } 34 35 type winConstraints struct { 36 minWidth, minHeight int32 37 maxWidth, maxHeight int32 38 } 39 40 type winDeltas struct { 41 width int32 42 height int32 43 } 44 45 type window struct { 46 hwnd syscall.Handle 47 hdc syscall.Handle 48 w Callbacks 49 width int 50 height int 51 stage system.Stage 52 pointerBtns pointer.Buttons 53 54 // cursorIn tracks whether the cursor was inside the window according 55 // to the most recent WM_SETCURSOR. 56 cursorIn bool 57 cursor syscall.Handle 58 59 // placement saves the previous window position when in full screen mode. 60 placement *windows.WindowPlacement 61 62 animating bool 63 64 minmax winConstraints 65 deltas winDeltas 66 opts *Options 67 } 68 69 const _WM_WAKEUP = windows.WM_USER + iota 70 71 type gpuAPI struct { 72 priority int 73 initializer func(w *window) (Context, error) 74 } 75 76 // drivers is the list of potential Context implementations. 77 var drivers []gpuAPI 78 79 // winMap maps win32 HWNDs to *windows. 80 var winMap sync.Map 81 82 // iconID is the ID of the icon in the resource file. 83 const iconID = 1 84 85 var resources struct { 86 once sync.Once 87 // handle is the module handle from GetModuleHandle. 88 handle syscall.Handle 89 // class is the Gio window class from RegisterClassEx. 90 class uint16 91 // cursor is the arrow cursor resource. 92 cursor syscall.Handle 93 } 94 95 func Main() { 96 select {} 97 } 98 99 func NewWindow(window Callbacks, opts *Options) error { 100 cerr := make(chan error) 101 go func() { 102 // GetMessage and PeekMessage can filter on a window HWND, but 103 // then thread-specific messages such as WM_QUIT are ignored. 104 // Instead lock the thread so window messages arrive through 105 // unfiltered GetMessage calls. 106 runtime.LockOSThread() 107 w, err := createNativeWindow(opts) 108 if err != nil { 109 cerr <- err 110 return 111 } 112 cerr <- nil 113 winMap.Store(w.hwnd, w) 114 defer winMap.Delete(w.hwnd) 115 w.w = window 116 w.w.SetDriver(w) 117 w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)}) 118 w.Option(opts) 119 windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT) 120 windows.SetForegroundWindow(w.hwnd) 121 windows.SetFocus(w.hwnd) 122 // Since the window class for the cursor is null, 123 // set it here to show the cursor. 124 w.SetCursor(pointer.CursorDefault) 125 if err := w.loop(); err != nil { 126 panic(err) 127 } 128 }() 129 return <-cerr 130 } 131 132 // initResources initializes the resources global. 133 func initResources() error { 134 windows.SetProcessDPIAware() 135 hInst, err := windows.GetModuleHandle() 136 if err != nil { 137 return err 138 } 139 resources.handle = hInst 140 c, err := windows.LoadCursor(windows.IDC_ARROW) 141 if err != nil { 142 return err 143 } 144 resources.cursor = c 145 icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED) 146 wcls := windows.WndClassEx{ 147 CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})), 148 Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC, 149 LpfnWndProc: syscall.NewCallback(windowProc), 150 HInstance: hInst, 151 HIcon: icon, 152 LpszClassName: syscall.StringToUTF16Ptr("GioWindow"), 153 } 154 cls, err := windows.RegisterClassEx(&wcls) 155 if err != nil { 156 return err 157 } 158 resources.class = cls 159 return nil 160 } 161 162 func getWindowConstraints(cfg unit.Metric, opts *Options) winConstraints { 163 var minmax winConstraints 164 if o := opts.MinSize; o != nil { 165 minmax.minWidth = int32(cfg.Px(o.Width)) 166 minmax.minHeight = int32(cfg.Px(o.Height)) 167 } 168 if o := opts.MaxSize; o != nil { 169 minmax.maxWidth = int32(cfg.Px(o.Width)) 170 minmax.maxHeight = int32(cfg.Px(o.Height)) 171 } 172 return minmax 173 } 174 175 func createNativeWindow(opts *Options) (*window, error) { 176 var resErr error 177 resources.once.Do(func() { 178 resErr = initResources() 179 }) 180 if resErr != nil { 181 return nil, resErr 182 } 183 dpi := windows.GetSystemDPI() 184 cfg := configForDPI(dpi) 185 dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW) 186 dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE) 187 188 hwnd, err := windows.CreateWindowEx(dwExStyle, 189 resources.class, 190 "", 191 dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN, 192 windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, 193 windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, 194 0, 195 0, 196 resources.handle, 197 0) 198 if err != nil { 199 return nil, err 200 } 201 w := &window{ 202 hwnd: hwnd, 203 minmax: getWindowConstraints(cfg, opts), 204 opts: opts, 205 } 206 w.hdc, err = windows.GetDC(hwnd) 207 if err != nil { 208 return nil, err 209 } 210 return w, nil 211 } 212 213 func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { 214 win, exists := winMap.Load(hwnd) 215 if !exists { 216 return windows.DefWindowProc(hwnd, msg, wParam, lParam) 217 } 218 219 w := win.(*window) 220 221 switch msg { 222 case windows.WM_UNICHAR: 223 if wParam == windows.UNICODE_NOCHAR { 224 // Tell the system that we accept WM_UNICHAR messages. 225 return windows.TRUE 226 } 227 fallthrough 228 case windows.WM_CHAR: 229 if r := rune(wParam); unicode.IsPrint(r) { 230 w.w.Event(key.EditEvent{Text: string(r)}) 231 } 232 // The message is processed. 233 return windows.TRUE 234 case windows.WM_DPICHANGED: 235 // Let Windows know we're prepared for runtime DPI changes. 236 return windows.TRUE 237 case windows.WM_ERASEBKGND: 238 // Avoid flickering between GPU content and background color. 239 return windows.TRUE 240 case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP: 241 if n, ok := convertKeyCode(wParam); ok { 242 e := key.Event{ 243 Name: n, 244 Modifiers: getModifiers(), 245 State: key.Press, 246 } 247 if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP { 248 e.State = key.Release 249 } 250 251 w.w.Event(e) 252 253 if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) { 254 // Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs 255 // such as cmd.exe and graphical debuggers also reserve F10. 256 return 0 257 } 258 } 259 case windows.WM_LBUTTONDOWN: 260 w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers()) 261 case windows.WM_LBUTTONUP: 262 w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers()) 263 case windows.WM_RBUTTONDOWN: 264 w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers()) 265 case windows.WM_RBUTTONUP: 266 w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers()) 267 case windows.WM_MBUTTONDOWN: 268 w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers()) 269 case windows.WM_MBUTTONUP: 270 w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers()) 271 case windows.WM_CANCELMODE: 272 w.w.Event(pointer.Event{ 273 Type: pointer.Cancel, 274 }) 275 case windows.WM_SETFOCUS: 276 w.w.Event(key.FocusEvent{Focus: true}) 277 case windows.WM_KILLFOCUS: 278 w.w.Event(key.FocusEvent{Focus: false}) 279 case windows.WM_MOUSEMOVE: 280 x, y := coordsFromlParam(lParam) 281 p := f32.Point{X: float32(x), Y: float32(y)} 282 w.w.Event(pointer.Event{ 283 Type: pointer.Move, 284 Source: pointer.Mouse, 285 Position: p, 286 Buttons: w.pointerBtns, 287 Time: windows.GetMessageTime(), 288 }) 289 case windows.WM_MOUSEWHEEL: 290 w.scrollEvent(wParam, lParam, false) 291 case windows.WM_MOUSEHWHEEL: 292 w.scrollEvent(wParam, lParam, true) 293 case windows.WM_DESTROY: 294 w.w.Event(ViewEvent{}) 295 w.w.Event(system.DestroyEvent{}) 296 if w.hdc != 0 { 297 windows.ReleaseDC(w.hdc) 298 w.hdc = 0 299 } 300 // The system destroys the HWND for us. 301 w.hwnd = 0 302 windows.PostQuitMessage(0) 303 case windows.WM_PAINT: 304 w.draw(true) 305 case windows.WM_SIZE: 306 switch wParam { 307 case windows.SIZE_MINIMIZED: 308 w.setStage(system.StagePaused) 309 case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED: 310 w.setStage(system.StageRunning) 311 } 312 case windows.WM_GETMINMAXINFO: 313 mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam))) 314 if w.minmax.minWidth > 0 || w.minmax.minHeight > 0 { 315 mm.PtMinTrackSize = windows.Point{ 316 X: w.minmax.minWidth + w.deltas.width, 317 Y: w.minmax.minHeight + w.deltas.height, 318 } 319 } 320 if w.minmax.maxWidth > 0 || w.minmax.maxHeight > 0 { 321 mm.PtMaxTrackSize = windows.Point{ 322 X: w.minmax.maxWidth + w.deltas.width, 323 Y: w.minmax.maxHeight + w.deltas.height, 324 } 325 } 326 case windows.WM_SETCURSOR: 327 w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT 328 if w.cursorIn { 329 windows.SetCursor(w.cursor) 330 return windows.TRUE 331 } 332 case _WM_WAKEUP: 333 w.w.Event(WakeupEvent{}) 334 } 335 336 return windows.DefWindowProc(hwnd, msg, wParam, lParam) 337 } 338 339 func getModifiers() key.Modifiers { 340 var kmods key.Modifiers 341 if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 { 342 kmods |= key.ModSuper 343 } 344 if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 { 345 kmods |= key.ModAlt 346 } 347 if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 { 348 kmods |= key.ModCtrl 349 } 350 if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 { 351 kmods |= key.ModShift 352 } 353 return kmods 354 } 355 356 func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) { 357 var typ pointer.Type 358 if press { 359 typ = pointer.Press 360 if w.pointerBtns == 0 { 361 windows.SetCapture(w.hwnd) 362 } 363 w.pointerBtns |= btn 364 } else { 365 typ = pointer.Release 366 w.pointerBtns &^= btn 367 if w.pointerBtns == 0 { 368 windows.ReleaseCapture() 369 } 370 } 371 x, y := coordsFromlParam(lParam) 372 p := f32.Point{X: float32(x), Y: float32(y)} 373 w.w.Event(pointer.Event{ 374 Type: typ, 375 Source: pointer.Mouse, 376 Position: p, 377 Buttons: w.pointerBtns, 378 Time: windows.GetMessageTime(), 379 Modifiers: kmods, 380 }) 381 } 382 383 func coordsFromlParam(lParam uintptr) (int, int) { 384 x := int(int16(lParam & 0xffff)) 385 y := int(int16((lParam >> 16) & 0xffff)) 386 return x, y 387 } 388 389 func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool) { 390 x, y := coordsFromlParam(lParam) 391 // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast 392 // to other mouse events. 393 np := windows.Point{X: int32(x), Y: int32(y)} 394 windows.ScreenToClient(w.hwnd, &np) 395 p := f32.Point{X: float32(np.X), Y: float32(np.Y)} 396 dist := float32(int16(wParam >> 16)) 397 var sp f32.Point 398 if horizontal { 399 sp.X = dist 400 } else { 401 sp.Y = -dist 402 } 403 w.w.Event(pointer.Event{ 404 Type: pointer.Scroll, 405 Source: pointer.Mouse, 406 Position: p, 407 Buttons: w.pointerBtns, 408 Scroll: sp, 409 Time: windows.GetMessageTime(), 410 }) 411 } 412 413 // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/ 414 func (w *window) loop() error { 415 msg := new(windows.Msg) 416 loop: 417 for { 418 anim := w.animating 419 if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) { 420 w.draw(false) 421 continue 422 } 423 switch ret := windows.GetMessage(msg, 0, 0, 0); ret { 424 case -1: 425 return errors.New("GetMessage failed") 426 case 0: 427 // WM_QUIT received. 428 break loop 429 } 430 windows.TranslateMessage(msg) 431 windows.DispatchMessage(msg) 432 } 433 return nil 434 } 435 436 func (w *window) SetAnimating(anim bool) { 437 w.animating = anim 438 } 439 440 func (w *window) Wakeup() { 441 if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil { 442 panic(err) 443 } 444 } 445 446 func (w *window) setStage(s system.Stage) { 447 w.stage = s 448 w.w.Event(system.StageEvent{Stage: s}) 449 } 450 451 func (w *window) draw(sync bool) { 452 var r windows.Rect 453 windows.GetClientRect(w.hwnd, &r) 454 w.width = int(r.Right - r.Left) 455 w.height = int(r.Bottom - r.Top) 456 if w.width == 0 || w.height == 0 { 457 return 458 } 459 dpi := windows.GetWindowDPI(w.hwnd) 460 cfg := configForDPI(dpi) 461 w.minmax = getWindowConstraints(cfg, w.opts) 462 w.w.Event(FrameEvent{ 463 FrameEvent: system.FrameEvent{ 464 Now: time.Now(), 465 Size: image.Point{ 466 X: w.width, 467 Y: w.height, 468 }, 469 Metric: cfg, 470 }, 471 Sync: sync, 472 }) 473 } 474 475 func (w *window) NewContext() (Context, error) { 476 sort.Slice(drivers, func(i, j int) bool { 477 return drivers[i].priority < drivers[j].priority 478 }) 479 var errs []string 480 for _, b := range drivers { 481 ctx, err := b.initializer(w) 482 if err == nil { 483 return ctx, nil 484 } 485 errs = append(errs, err.Error()) 486 } 487 if len(errs) > 0 { 488 return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", ")) 489 } 490 return nil, errors.New("NewContext: no available GPU drivers") 491 } 492 493 func (w *window) ReadClipboard() { 494 w.readClipboard() 495 } 496 497 func (w *window) readClipboard() error { 498 if err := windows.OpenClipboard(w.hwnd); err != nil { 499 return err 500 } 501 defer windows.CloseClipboard() 502 mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT) 503 if err != nil { 504 return err 505 } 506 ptr, err := windows.GlobalLock(mem) 507 if err != nil { 508 return err 509 } 510 defer windows.GlobalUnlock(mem) 511 content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr))) 512 go func() { 513 w.w.Event(clipboard.Event{Text: content}) 514 }() 515 return nil 516 } 517 518 func (w *window) Option(opts *Options) { 519 w.opts = opts 520 if o := opts.Size; o != nil { 521 dpi := windows.GetSystemDPI() 522 cfg := configForDPI(dpi) 523 width := int32(cfg.Px(o.Width)) 524 height := int32(cfg.Px(o.Height)) 525 526 // Include the window decorations. 527 wr := windows.Rect{ 528 Right: width, 529 Bottom: height, 530 } 531 dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW) 532 dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE) 533 windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle) 534 535 dw, dh := width, height 536 width = wr.Right - wr.Left 537 height = wr.Bottom - wr.Top 538 w.deltas.width = width - dw 539 w.deltas.height = height - dh 540 541 w.opts.Size = o 542 windows.MoveWindow(w.hwnd, 0, 0, width, height, true) 543 } 544 if o := opts.MinSize; o != nil { 545 w.opts.MinSize = o 546 } 547 if o := opts.MaxSize; o != nil { 548 w.opts.MaxSize = o 549 } 550 if o := opts.Title; o != nil { 551 windows.SetWindowText(w.hwnd, *opts.Title) 552 } 553 if o := opts.WindowMode; o != nil { 554 w.SetWindowMode(*o) 555 } 556 } 557 558 func (w *window) SetWindowMode(mode WindowMode) { 559 // https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353 560 switch mode { 561 case Windowed: 562 if w.placement == nil { 563 return 564 } 565 windows.SetWindowPlacement(w.hwnd, w.placement) 566 w.placement = nil 567 style := windows.GetWindowLong(w.hwnd) 568 windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW) 569 windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 570 0, 0, 0, 0, 571 windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED, 572 ) 573 case Fullscreen: 574 if w.placement != nil { 575 return 576 } 577 w.placement = windows.GetWindowPlacement(w.hwnd) 578 style := windows.GetWindowLong(w.hwnd) 579 windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style&^windows.WS_OVERLAPPEDWINDOW) 580 mi := windows.GetMonitorInfo(w.hwnd) 581 windows.SetWindowPos(w.hwnd, 0, 582 mi.Monitor.Left, mi.Monitor.Top, 583 mi.Monitor.Right-mi.Monitor.Left, 584 mi.Monitor.Bottom-mi.Monitor.Top, 585 windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED, 586 ) 587 } 588 } 589 590 func (w *window) WriteClipboard(s string) { 591 w.writeClipboard(s) 592 } 593 594 func (w *window) writeClipboard(s string) error { 595 if err := windows.OpenClipboard(w.hwnd); err != nil { 596 return err 597 } 598 defer windows.CloseClipboard() 599 if err := windows.EmptyClipboard(); err != nil { 600 return err 601 } 602 u16, err := gowindows.UTF16FromString(s) 603 if err != nil { 604 return err 605 } 606 n := len(u16) * int(unsafe.Sizeof(u16[0])) 607 mem, err := windows.GlobalAlloc(n) 608 if err != nil { 609 return err 610 } 611 ptr, err := windows.GlobalLock(mem) 612 if err != nil { 613 windows.GlobalFree(mem) 614 return err 615 } 616 var u16v []uint16 617 hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16v)) 618 hdr.Data = ptr 619 hdr.Cap = len(u16) 620 hdr.Len = len(u16) 621 copy(u16v, u16) 622 windows.GlobalUnlock(mem) 623 if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil { 624 windows.GlobalFree(mem) 625 return err 626 } 627 return nil 628 } 629 630 func (w *window) SetCursor(name pointer.CursorName) { 631 c, err := loadCursor(name) 632 if err != nil { 633 c = resources.cursor 634 } 635 w.cursor = c 636 if w.cursorIn { 637 windows.SetCursor(w.cursor) 638 } 639 } 640 641 func loadCursor(name pointer.CursorName) (syscall.Handle, error) { 642 var curID uint16 643 switch name { 644 default: 645 fallthrough 646 case pointer.CursorDefault: 647 return resources.cursor, nil 648 case pointer.CursorText: 649 curID = windows.IDC_IBEAM 650 case pointer.CursorPointer: 651 curID = windows.IDC_HAND 652 case pointer.CursorCrossHair: 653 curID = windows.IDC_CROSS 654 case pointer.CursorColResize: 655 curID = windows.IDC_SIZEWE 656 case pointer.CursorRowResize: 657 curID = windows.IDC_SIZENS 658 case pointer.CursorGrab: 659 curID = windows.IDC_SIZEALL 660 case pointer.CursorNone: 661 return 0, nil 662 } 663 return windows.LoadCursor(curID) 664 } 665 666 func (w *window) ShowTextInput(show bool) {} 667 668 func (w *window) SetInputHint(_ key.InputHint) {} 669 670 func (w *window) HDC() syscall.Handle { 671 return w.hdc 672 } 673 674 func (w *window) HWND() (syscall.Handle, int, int) { 675 return w.hwnd, w.width, w.height 676 } 677 678 func (w *window) Close() { 679 windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0) 680 } 681 682 func convertKeyCode(code uintptr) (string, bool) { 683 if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' { 684 return string(rune(code)), true 685 } 686 var r string 687 switch code { 688 case windows.VK_ESCAPE: 689 r = key.NameEscape 690 case windows.VK_LEFT: 691 r = key.NameLeftArrow 692 case windows.VK_RIGHT: 693 r = key.NameRightArrow 694 case windows.VK_RETURN: 695 r = key.NameReturn 696 case windows.VK_UP: 697 r = key.NameUpArrow 698 case windows.VK_DOWN: 699 r = key.NameDownArrow 700 case windows.VK_HOME: 701 r = key.NameHome 702 case windows.VK_END: 703 r = key.NameEnd 704 case windows.VK_BACK: 705 r = key.NameDeleteBackward 706 case windows.VK_DELETE: 707 r = key.NameDeleteForward 708 case windows.VK_PRIOR: 709 r = key.NamePageUp 710 case windows.VK_NEXT: 711 r = key.NamePageDown 712 case windows.VK_F1: 713 r = "F1" 714 case windows.VK_F2: 715 r = "F2" 716 case windows.VK_F3: 717 r = "F3" 718 case windows.VK_F4: 719 r = "F4" 720 case windows.VK_F5: 721 r = "F5" 722 case windows.VK_F6: 723 r = "F6" 724 case windows.VK_F7: 725 r = "F7" 726 case windows.VK_F8: 727 r = "F8" 728 case windows.VK_F9: 729 r = "F9" 730 case windows.VK_F10: 731 r = "F10" 732 case windows.VK_F11: 733 r = "F11" 734 case windows.VK_F12: 735 r = "F12" 736 case windows.VK_TAB: 737 r = key.NameTab 738 case windows.VK_SPACE: 739 r = key.NameSpace 740 case windows.VK_OEM_1: 741 r = ";" 742 case windows.VK_OEM_PLUS: 743 r = "+" 744 case windows.VK_OEM_COMMA: 745 r = "," 746 case windows.VK_OEM_MINUS: 747 r = "-" 748 case windows.VK_OEM_PERIOD: 749 r = "." 750 case windows.VK_OEM_2: 751 r = "/" 752 case windows.VK_OEM_3: 753 r = "`" 754 case windows.VK_OEM_4: 755 r = "[" 756 case windows.VK_OEM_5, windows.VK_OEM_102: 757 r = "\\" 758 case windows.VK_OEM_6: 759 r = "]" 760 case windows.VK_OEM_7: 761 r = "'" 762 default: 763 return "", false 764 } 765 return r, true 766 } 767 768 func configForDPI(dpi int) unit.Metric { 769 const inchPrDp = 1.0 / 96.0 770 ppdp := float32(dpi) * inchPrDp 771 return unit.Metric{ 772 PxPerDp: ppdp, 773 PxPerSp: ppdp, 774 } 775 } 776 777 func (_ ViewEvent) ImplementsEvent() {}