diff options
Diffstat (limited to 'vendor/gioui.org/app/os_windows.go')
-rw-r--r-- | vendor/gioui.org/app/os_windows.go | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/vendor/gioui.org/app/os_windows.go b/vendor/gioui.org/app/os_windows.go new file mode 100644 index 0000000..ecbffb4 --- /dev/null +++ b/vendor/gioui.org/app/os_windows.go @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package app + +import ( + "errors" + "fmt" + "image" + "reflect" + "runtime" + "sort" + "strings" + "sync" + "time" + "unicode" + "unsafe" + + syscall "golang.org/x/sys/windows" + + "gioui.org/app/internal/windows" + "gioui.org/unit" + gowindows "golang.org/x/sys/windows" + + "gioui.org/f32" + "gioui.org/io/clipboard" + "gioui.org/io/key" + "gioui.org/io/pointer" + "gioui.org/io/system" +) + +type ViewEvent struct { + HWND uintptr +} + +type winDeltas struct { + width int32 + height int32 +} + +type window struct { + hwnd syscall.Handle + hdc syscall.Handle + w *callbacks + stage system.Stage + pointerBtns pointer.Buttons + + // cursorIn tracks whether the cursor was inside the window according + // to the most recent WM_SETCURSOR. + cursorIn bool + cursor syscall.Handle + + // placement saves the previous window position when in full screen mode. + placement *windows.WindowPlacement + + animating bool + + deltas winDeltas + config Config +} + +const _WM_WAKEUP = windows.WM_USER + iota + +type gpuAPI struct { + priority int + initializer func(w *window) (context, error) +} + +// drivers is the list of potential Context implementations. +var drivers []gpuAPI + +// winMap maps win32 HWNDs to *windows. +var winMap sync.Map + +// iconID is the ID of the icon in the resource file. +const iconID = 1 + +var resources struct { + once sync.Once + // handle is the module handle from GetModuleHandle. + handle syscall.Handle + // class is the Gio window class from RegisterClassEx. + class uint16 + // cursor is the arrow cursor resource. + cursor syscall.Handle +} + +func osMain() { + select {} +} + +func newWindow(window *callbacks, options []Option) error { + cerr := make(chan error) + go func() { + // GetMessage and PeekMessage can filter on a window HWND, but + // then thread-specific messages such as WM_QUIT are ignored. + // Instead lock the thread so window messages arrive through + // unfiltered GetMessage calls. + runtime.LockOSThread() + w, err := createNativeWindow() + if err != nil { + cerr <- err + return + } + cerr <- nil + winMap.Store(w.hwnd, w) + defer winMap.Delete(w.hwnd) + w.w = window + w.w.SetDriver(w) + w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)}) + w.Configure(options) + windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT) + windows.SetForegroundWindow(w.hwnd) + windows.SetFocus(w.hwnd) + // Since the window class for the cursor is null, + // set it here to show the cursor. + w.SetCursor(pointer.CursorDefault) + if err := w.loop(); err != nil { + panic(err) + } + }() + return <-cerr +} + +// initResources initializes the resources global. +func initResources() error { + windows.SetProcessDPIAware() + hInst, err := windows.GetModuleHandle() + if err != nil { + return err + } + resources.handle = hInst + c, err := windows.LoadCursor(windows.IDC_ARROW) + if err != nil { + return err + } + resources.cursor = c + icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED) + wcls := windows.WndClassEx{ + CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})), + Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC, + LpfnWndProc: syscall.NewCallback(windowProc), + HInstance: hInst, + HIcon: icon, + LpszClassName: syscall.StringToUTF16Ptr("GioWindow"), + } + cls, err := windows.RegisterClassEx(&wcls) + if err != nil { + return err + } + resources.class = cls + return nil +} + +func createNativeWindow() (*window, error) { + var resErr error + resources.once.Do(func() { + resErr = initResources() + }) + if resErr != nil { + return nil, resErr + } + dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW) + dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE) + + hwnd, err := windows.CreateWindowEx(dwExStyle, + resources.class, + "", + dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN, + windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, + windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, + 0, + 0, + resources.handle, + 0) + if err != nil { + return nil, err + } + w := &window{ + hwnd: hwnd, + } + w.hdc, err = windows.GetDC(hwnd) + if err != nil { + return nil, err + } + return w, nil +} + +func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { + win, exists := winMap.Load(hwnd) + if !exists { + return windows.DefWindowProc(hwnd, msg, wParam, lParam) + } + + w := win.(*window) + + switch msg { + case windows.WM_UNICHAR: + if wParam == windows.UNICODE_NOCHAR { + // Tell the system that we accept WM_UNICHAR messages. + return windows.TRUE + } + fallthrough + case windows.WM_CHAR: + if r := rune(wParam); unicode.IsPrint(r) { + w.w.Event(key.EditEvent{Text: string(r)}) + } + // The message is processed. + return windows.TRUE + case windows.WM_DPICHANGED: + // Let Windows know we're prepared for runtime DPI changes. + return windows.TRUE + case windows.WM_ERASEBKGND: + // Avoid flickering between GPU content and background color. + return windows.TRUE + case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP: + if n, ok := convertKeyCode(wParam); ok { + e := key.Event{ + Name: n, + Modifiers: getModifiers(), + State: key.Press, + } + if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP { + e.State = key.Release + } + + w.w.Event(e) + + if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) { + // Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs + // such as cmd.exe and graphical debuggers also reserve F10. + return 0 + } + } + case windows.WM_LBUTTONDOWN: + w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers()) + case windows.WM_LBUTTONUP: + w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers()) + case windows.WM_RBUTTONDOWN: + w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers()) + case windows.WM_RBUTTONUP: + w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers()) + case windows.WM_MBUTTONDOWN: + w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers()) + case windows.WM_MBUTTONUP: + w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers()) + case windows.WM_CANCELMODE: + w.w.Event(pointer.Event{ + Type: pointer.Cancel, + }) + case windows.WM_SETFOCUS: + w.w.Event(key.FocusEvent{Focus: true}) + case windows.WM_KILLFOCUS: + w.w.Event(key.FocusEvent{Focus: false}) + case windows.WM_MOUSEMOVE: + x, y := coordsFromlParam(lParam) + p := f32.Point{X: float32(x), Y: float32(y)} + w.w.Event(pointer.Event{ + Type: pointer.Move, + Source: pointer.Mouse, + Position: p, + Buttons: w.pointerBtns, + Time: windows.GetMessageTime(), + }) + case windows.WM_MOUSEWHEEL: + w.scrollEvent(wParam, lParam, false) + case windows.WM_MOUSEHWHEEL: + w.scrollEvent(wParam, lParam, true) + case windows.WM_DESTROY: + w.w.Event(ViewEvent{}) + w.w.Event(system.DestroyEvent{}) + if w.hdc != 0 { + windows.ReleaseDC(w.hdc) + w.hdc = 0 + } + // The system destroys the HWND for us. + w.hwnd = 0 + windows.PostQuitMessage(0) + case windows.WM_PAINT: + w.draw(true) + case windows.WM_SIZE: + switch wParam { + case windows.SIZE_MINIMIZED: + w.setStage(system.StagePaused) + case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED: + var triggerEvent bool + // Check the window size change. + var r windows.Rect + windows.GetClientRect(w.hwnd, &r) + size := image.Point{ + X: int(r.Right - r.Left), + Y: int(r.Bottom - r.Top), + } + if size != w.config.Size { + w.config.Size = size + triggerEvent = true + } + // Check the window mode. + mode := w.config.Mode + w.updateWindowMode() + if mode != w.config.Mode { + triggerEvent = true + } + if triggerEvent { + w.w.Event(ConfigEvent{Config: w.config}) + } + w.setStage(system.StageRunning) + } + case windows.WM_GETMINMAXINFO: + mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam))) + if p := w.config.MinSize; p.X > 0 || p.Y > 0 { + mm.PtMinTrackSize = windows.Point{ + X: int32(p.X) + w.deltas.width, + Y: int32(p.Y) + w.deltas.height, + } + } + if p := w.config.MaxSize; p.X > 0 || p.Y > 0 { + mm.PtMaxTrackSize = windows.Point{ + X: int32(p.X) + w.deltas.width, + Y: int32(p.Y) + w.deltas.height, + } + } + case windows.WM_SETCURSOR: + w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT + if w.cursorIn { + windows.SetCursor(w.cursor) + return windows.TRUE + } + case _WM_WAKEUP: + w.w.Event(wakeupEvent{}) + } + + return windows.DefWindowProc(hwnd, msg, wParam, lParam) +} + +func getModifiers() key.Modifiers { + var kmods key.Modifiers + if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 { + kmods |= key.ModSuper + } + if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 { + kmods |= key.ModAlt + } + if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 { + kmods |= key.ModCtrl + } + if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 { + kmods |= key.ModShift + } + return kmods +} + +func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) { + var typ pointer.Type + if press { + typ = pointer.Press + if w.pointerBtns == 0 { + windows.SetCapture(w.hwnd) + } + w.pointerBtns |= btn + } else { + typ = pointer.Release + w.pointerBtns &^= btn + if w.pointerBtns == 0 { + windows.ReleaseCapture() + } + } + x, y := coordsFromlParam(lParam) + p := f32.Point{X: float32(x), Y: float32(y)} + w.w.Event(pointer.Event{ + Type: typ, + Source: pointer.Mouse, + Position: p, + Buttons: w.pointerBtns, + Time: windows.GetMessageTime(), + Modifiers: kmods, + }) +} + +func coordsFromlParam(lParam uintptr) (int, int) { + x := int(int16(lParam & 0xffff)) + y := int(int16((lParam >> 16) & 0xffff)) + return x, y +} + +func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool) { + x, y := coordsFromlParam(lParam) + // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast + // to other mouse events. + np := windows.Point{X: int32(x), Y: int32(y)} + windows.ScreenToClient(w.hwnd, &np) + p := f32.Point{X: float32(np.X), Y: float32(np.Y)} + dist := float32(int16(wParam >> 16)) + var sp f32.Point + if horizontal { + sp.X = dist + } else { + sp.Y = -dist + } + w.w.Event(pointer.Event{ + Type: pointer.Scroll, + Source: pointer.Mouse, + Position: p, + Buttons: w.pointerBtns, + Scroll: sp, + Time: windows.GetMessageTime(), + }) +} + +// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/ +func (w *window) loop() error { + msg := new(windows.Msg) +loop: + for { + anim := w.animating + if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) { + w.draw(false) + continue + } + switch ret := windows.GetMessage(msg, 0, 0, 0); ret { + case -1: + return errors.New("GetMessage failed") + case 0: + // WM_QUIT received. + break loop + } + windows.TranslateMessage(msg) + windows.DispatchMessage(msg) + } + return nil +} + +func (w *window) SetAnimating(anim bool) { + w.animating = anim +} + +func (w *window) Wakeup() { + if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil { + panic(err) + } +} + +func (w *window) setStage(s system.Stage) { + if s != w.stage { + w.stage = s + w.w.Event(system.StageEvent{Stage: s}) + } +} + +func (w *window) draw(sync bool) { + if w.config.Size.X == 0 || w.config.Size.Y == 0 { + return + } + dpi := windows.GetWindowDPI(w.hwnd) + cfg := configForDPI(dpi) + w.w.Event(frameEvent{ + FrameEvent: system.FrameEvent{ + Now: time.Now(), + Size: w.config.Size, + Metric: cfg, + }, + Sync: sync, + }) +} + +func (w *window) NewContext() (context, error) { + sort.Slice(drivers, func(i, j int) bool { + return drivers[i].priority < drivers[j].priority + }) + var errs []string + for _, b := range drivers { + ctx, err := b.initializer(w) + if err == nil { + return ctx, nil + } + errs = append(errs, err.Error()) + } + if len(errs) > 0 { + return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", ")) + } + return nil, errors.New("NewContext: no available GPU drivers") +} + +func (w *window) ReadClipboard() { + w.readClipboard() +} + +func (w *window) readClipboard() error { + if err := windows.OpenClipboard(w.hwnd); err != nil { + return err + } + defer windows.CloseClipboard() + mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT) + if err != nil { + return err + } + ptr, err := windows.GlobalLock(mem) + if err != nil { + return err + } + defer windows.GlobalUnlock(mem) + content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr))) + w.w.Event(clipboard.Event{Text: content}) + return nil +} + +func (w *window) updateWindowMode() { + p := windows.GetWindowPlacement(w.hwnd) + r := p.Rect() + mi := windows.GetMonitorInfo(w.hwnd) + if r == mi.Monitor { + w.config.Mode = Fullscreen + } else { + w.config.Mode = Windowed + } +} + +func (w *window) Configure(options []Option) { + dpi := windows.GetSystemDPI() + cfg := configForDPI(dpi) + prev := w.config + w.updateWindowMode() + cnf := w.config + cnf.apply(cfg, options) + + if cnf.Mode != Fullscreen && prev.Size != cnf.Size { + width := int32(cnf.Size.X) + height := int32(cnf.Size.Y) + w.config.Size = cnf.Size + + // Include the window decorations. + wr := windows.Rect{ + Right: width, + Bottom: height, + } + dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW) + dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE) + windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle) + + dw, dh := width, height + width = wr.Right - wr.Left + height = wr.Bottom - wr.Top + w.deltas.width = width - dw + w.deltas.height = height - dh + + windows.MoveWindow(w.hwnd, 0, 0, width, height, true) + } + if prev.MinSize != cnf.MinSize { + w.config.MinSize = cnf.MinSize + } + if prev.MaxSize != cnf.MaxSize { + w.config.MaxSize = cnf.MaxSize + } + if prev.Title != cnf.Title { + w.config.Title = cnf.Title + windows.SetWindowText(w.hwnd, cnf.Title) + } + if prev.Mode != cnf.Mode { + w.SetWindowMode(cnf.Mode) + } + if w.config != prev { + w.w.Event(ConfigEvent{Config: w.config}) + } +} + +// Maximize the window. It will have no effect when in fullscreen mode. +func (w *window) Maximize() { + if w.config.Mode == Fullscreen { + return + } + style := windows.GetWindowLong(w.hwnd) + windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW|windows.WS_MAXIMIZE) + mi := windows.GetMonitorInfo(w.hwnd) + windows.SetWindowPos(w.hwnd, 0, + mi.Monitor.Left, mi.Monitor.Top, + mi.Monitor.Right-mi.Monitor.Left, + mi.Monitor.Bottom-mi.Monitor.Top, + windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED, + ) +} + +// Center will place window at monitor center. +func (w *window) Center() { + // Make sure that the window is sizeable + style := windows.GetWindowLong(w.hwnd) & (^uintptr(windows.WS_MAXIMIZE)) + windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW) + + // Find with/height including the window decorations. + wr := windows.Rect{ + Right: int32(w.config.Size.X), + Bottom: int32(w.config.Size.Y), + } + dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW) + dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE) + windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle) + width := wr.Right - wr.Left + height := wr.Bottom - wr.Top + + // Move to center of current monitor + mi := windows.GetMonitorInfo(w.hwnd).Monitor + x := mi.Left + (mi.Right-mi.Left-width)/2 + y := mi.Top + (mi.Bottom-mi.Top-height)/2 + windows.MoveWindow(w.hwnd, x, y, width, height, true) +} + +func (w *window) SetWindowMode(mode WindowMode) { + // https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353 + switch mode { + case Windowed: + if w.placement == nil { + return + } + w.config.Mode = Windowed + windows.SetWindowPlacement(w.hwnd, w.placement) + w.placement = nil + style := windows.GetWindowLong(w.hwnd) + windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW) + windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, + 0, 0, 0, 0, + windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED, + ) + case Fullscreen: + if w.placement != nil { + return + } + w.config.Mode = Fullscreen + w.placement = windows.GetWindowPlacement(w.hwnd) + style := windows.GetWindowLong(w.hwnd) + windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style&^windows.WS_OVERLAPPEDWINDOW) + mi := windows.GetMonitorInfo(w.hwnd) + windows.SetWindowPos(w.hwnd, 0, + mi.Monitor.Left, mi.Monitor.Top, + mi.Monitor.Right-mi.Monitor.Left, + mi.Monitor.Bottom-mi.Monitor.Top, + windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED, + ) + } +} + +func (w *window) WriteClipboard(s string) { + w.writeClipboard(s) +} + +func (w *window) writeClipboard(s string) error { + if err := windows.OpenClipboard(w.hwnd); err != nil { + return err + } + defer windows.CloseClipboard() + if err := windows.EmptyClipboard(); err != nil { + return err + } + u16, err := gowindows.UTF16FromString(s) + if err != nil { + return err + } + n := len(u16) * int(unsafe.Sizeof(u16[0])) + mem, err := windows.GlobalAlloc(n) + if err != nil { + return err + } + ptr, err := windows.GlobalLock(mem) + if err != nil { + windows.GlobalFree(mem) + return err + } + var u16v []uint16 + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16v)) + hdr.Data = ptr + hdr.Cap = len(u16) + hdr.Len = len(u16) + copy(u16v, u16) + windows.GlobalUnlock(mem) + if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil { + windows.GlobalFree(mem) + return err + } + return nil +} + +func (w *window) SetCursor(name pointer.CursorName) { + c, err := loadCursor(name) + if err != nil { + c = resources.cursor + } + w.cursor = c + if w.cursorIn { + windows.SetCursor(w.cursor) + } +} + +func loadCursor(name pointer.CursorName) (syscall.Handle, error) { + var curID uint16 + switch name { + default: + fallthrough + case pointer.CursorDefault: + return resources.cursor, nil + case pointer.CursorText: + curID = windows.IDC_IBEAM + case pointer.CursorPointer: + curID = windows.IDC_HAND + case pointer.CursorCrossHair: + curID = windows.IDC_CROSS + case pointer.CursorColResize: + curID = windows.IDC_SIZEWE + case pointer.CursorRowResize: + curID = windows.IDC_SIZENS + case pointer.CursorGrab: + curID = windows.IDC_SIZEALL + case pointer.CursorNone: + return 0, nil + } + return windows.LoadCursor(curID) +} + +func (w *window) ShowTextInput(show bool) {} + +func (w *window) SetInputHint(_ key.InputHint) {} + +func (w *window) HDC() syscall.Handle { + return w.hdc +} + +func (w *window) HWND() (syscall.Handle, int, int) { + return w.hwnd, w.config.Size.X, w.config.Size.Y +} + +func (w *window) Close() { + windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0) +} + +func (w *window) Raise() { + windows.SetForegroundWindow(w.hwnd) + windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0, + windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW) +} + +func convertKeyCode(code uintptr) (string, bool) { + if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' { + return string(rune(code)), true + } + var r string + switch code { + case windows.VK_ESCAPE: + r = key.NameEscape + case windows.VK_LEFT: + r = key.NameLeftArrow + case windows.VK_RIGHT: + r = key.NameRightArrow + case windows.VK_RETURN: + r = key.NameReturn + case windows.VK_UP: + r = key.NameUpArrow + case windows.VK_DOWN: + r = key.NameDownArrow + case windows.VK_HOME: + r = key.NameHome + case windows.VK_END: + r = key.NameEnd + case windows.VK_BACK: + r = key.NameDeleteBackward + case windows.VK_DELETE: + r = key.NameDeleteForward + case windows.VK_PRIOR: + r = key.NamePageUp + case windows.VK_NEXT: + r = key.NamePageDown + case windows.VK_F1: + r = "F1" + case windows.VK_F2: + r = "F2" + case windows.VK_F3: + r = "F3" + case windows.VK_F4: + r = "F4" + case windows.VK_F5: + r = "F5" + case windows.VK_F6: + r = "F6" + case windows.VK_F7: + r = "F7" + case windows.VK_F8: + r = "F8" + case windows.VK_F9: + r = "F9" + case windows.VK_F10: + r = "F10" + case windows.VK_F11: + r = "F11" + case windows.VK_F12: + r = "F12" + case windows.VK_TAB: + r = key.NameTab + case windows.VK_SPACE: + r = key.NameSpace + case windows.VK_OEM_1: + r = ";" + case windows.VK_OEM_PLUS: + r = "+" + case windows.VK_OEM_COMMA: + r = "," + case windows.VK_OEM_MINUS: + r = "-" + case windows.VK_OEM_PERIOD: + r = "." + case windows.VK_OEM_2: + r = "/" + case windows.VK_OEM_3: + r = "`" + case windows.VK_OEM_4: + r = "[" + case windows.VK_OEM_5, windows.VK_OEM_102: + r = "\\" + case windows.VK_OEM_6: + r = "]" + case windows.VK_OEM_7: + r = "'" + default: + return "", false + } + return r, true +} + +func configForDPI(dpi int) unit.Metric { + const inchPrDp = 1.0 / 96.0 + ppdp := float32(dpi) * inchPrDp + return unit.Metric{ + PxPerDp: ppdp, + PxPerSp: ppdp, + } +} + +func (_ ViewEvent) ImplementsEvent() {} |