github.com/utopiagio/gio@v0.0.8/app/os_windows.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package app
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"image"
     9  	"io"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  	"unicode"
    16  	"unicode/utf8"
    17  	"unsafe"
    18  
    19  	syscall "golang.org/x/sys/windows"
    20  
    21  	"github.com/utopiagio/gio/app/internal/windows"
    22  	"github.com/utopiagio/gio/op"
    23  	"github.com/utopiagio/gio/unit"
    24  	gowindows "golang.org/x/sys/windows"
    25  
    26  	"github.com/utopiagio/gio/f32"
    27  	"github.com/utopiagio/gio/io/event"
    28  	"github.com/utopiagio/gio/io/key"
    29  	"github.com/utopiagio/gio/io/pointer"
    30  	"github.com/utopiagio/gio/io/system"
    31  	"github.com/utopiagio/gio/io/transfer"
    32  )
    33  
    34  type Win32ViewEvent struct {
    35  	HWND uintptr
    36  }
    37  
    38  type window struct {
    39  	hwnd        syscall.Handle
    40  	hdc         syscall.Handle
    41  	w           *callbacks
    42  	pointerBtns pointer.Buttons
    43  
    44  	// cursorIn tracks whether the cursor was inside the window according
    45  	// to the most recent WM_SETCURSOR.
    46  	cursorIn bool
    47  	cursor   syscall.Handle
    48  
    49  	// placement saves the previous window position when in full screen mode.
    50  	placement *windows.WindowPlacement
    51  
    52  	animating bool
    53  
    54  	borderSize image.Point
    55  	config     Config
    56  	loop       *eventLoop
    57  
    58  	// invMu avoids the race between destroying the window and Invalidate.
    59  	invMu sync.Mutex
    60  }
    61  
    62  const _WM_WAKEUP = windows.WM_USER + iota
    63  
    64  type gpuAPI struct {
    65  	priority    int
    66  	initializer func(w *window) (context, error)
    67  }
    68  
    69  // drivers is the list of potential Context implementations.
    70  var drivers []gpuAPI
    71  
    72  // winMap maps win32 HWNDs to *windows.
    73  var winMap sync.Map
    74  
    75  // iconID is the ID of the icon in the resource file.
    76  const iconID = 1
    77  
    78  var resources struct {
    79  	once sync.Once
    80  	// handle is the module handle from GetModuleHandle.
    81  	handle syscall.Handle
    82  	// class is the Gio window class from RegisterClassEx.
    83  	class uint16
    84  	// cursor is the arrow cursor resource.
    85  	cursor syscall.Handle
    86  }
    87  
    88  func osMain() {
    89  	select {}
    90  }
    91  
    92  
    93  func newWindow(win *callbacks, options []Option) {
    94  	//fmt.Println("newWindow............")
    95  	done := make(chan struct{})
    96  	go func() {
    97  		// GetMessage and PeekMessage can filter on a window HWND, but
    98  		// then thread-specific messages such as WM_QUIT are ignored.
    99  		// Instead lock the thread so window messages arrive through
   100  		// unfiltered GetMessage calls.
   101  		runtime.LockOSThread()
   102  
   103  		w := &window{
   104  			w: win,
   105  		}
   106  		w.loop = newEventLoop(w.w, w.wakeup)
   107  		w.w.SetDriver(w)
   108  		err := w.init()
   109  		done <- struct{}{}
   110  		if err != nil {
   111  			w.ProcessEvent(DestroyEvent{Err: err})
   112  			return
   113  		}
   114  		winMap.Store(w.hwnd, w)
   115  		defer winMap.Delete(w.hwnd)
   116  		w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
   117  		w.Configure(options)
   118  		windows.SetForegroundWindow(w.hwnd)
   119  		windows.SetFocus(w.hwnd)
   120  		// Since the window class for the cursor is null,
   121  		// set it here to show the cursor.
   122  		w.SetCursor(pointer.CursorDefault)
   123  		w.runLoop()
   124  	}()
   125  	<-done
   126  }
   127  
   128  // initResources initializes the resources global.
   129  func initResources() error {
   130  	windows.SetProcessDPIAware()
   131  	hInst, err := windows.GetModuleHandle()
   132  	if err != nil {
   133  		return err
   134  	}
   135  	resources.handle = hInst
   136  	c, err := windows.LoadCursor(windows.IDC_ARROW)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	resources.cursor = c
   141  	icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
   142  	wcls := windows.WndClassEx{
   143  		CbSize:        uint32(unsafe.Sizeof(windows.WndClassEx{})),
   144  		Style:         windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
   145  		LpfnWndProc:   syscall.NewCallback(windowProc),
   146  		HInstance:     hInst,
   147  		HIcon:         icon,
   148  		LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
   149  	}
   150  	cls, err := windows.RegisterClassEx(&wcls)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	resources.class = cls
   155  	return nil
   156  }
   157  
   158  const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
   159  
   160  func (w *window) init() error {
   161  	var resErr error
   162  	resources.once.Do(func() {
   163  		resErr = initResources()
   164  	})
   165  	if resErr != nil {
   166  		return resErr
   167  	}
   168  	const dwStyle = windows.WS_OVERLAPPEDWINDOW
   169  
   170  	hwnd, err := windows.CreateWindowEx(
   171  		dwExStyle,
   172  		resources.class,
   173  		"",
   174  		dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
   175  		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
   176  		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
   177  		0,
   178  		0,
   179  		resources.handle,
   180  		0)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	w.hdc, err = windows.GetDC(hwnd)
   185  	if err != nil {
   186  		windows.DestroyWindow(hwnd)
   187  		return err
   188  	}
   189  	w.hwnd = hwnd
   190  	return nil
   191  }
   192  
   193  // update() handles changes done by the user, and updates the configuration.
   194  // It reads the window style and size/position and updates w.config.
   195  // If anything has changed it emits a ConfigEvent to notify the application.
   196  func (w *window) update() {
   197  	w.borderSize = image.Pt(
   198  		windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
   199  		windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
   200  	)
   201  
   202  	cr := windows.GetClientRect(w.hwnd)
   203  	//fmt.Println("w.GetClientRect=", cr.Left, ",", cr.Top, ",", cr.Right - cr.Left, ",", cr.Bottom - cr.Top, ",", ")")
   204  	w.config.Size = image.Point{
   205  		X: int(cr.Right - cr.Left),
   206  		Y: int(cr.Bottom - cr.Top),
   207  	}
   208  	//fmt.Println("w.client.Size Width=", w.config.Size.X, "Height=", w.config.Size.Y, ")")
   209  	// *************************************************************************
   210  	// RNW Added window position to update config after WM_MOVE message received
   211  	wr := windows.GetWindowRect(w.hwnd)
   212  	//fmt.Println("w.GetWindowRect=", wr.Left, ",", wr.Top, ",", wr.Right - wr.Left, ",", wr.Bottom - wr.Top, ",", ")")
   213  	w.config.Pos = image.Point{
   214  		X: int(wr.Left),
   215  		Y: int(wr.Top),
   216  	}
   217  	//X := int(wr.Right - wr.Left)
   218  	//Y := int(wr.Bottom - wr.Top)
   219  
   220  	//fmt.Println("w.window.Pos X=", w.config.Pos.X, "Y=", w.config.Pos.Y, ")")
   221  	//fmt.Println("w.window.Size Width=", X, "Height=", Y, ")")
   222  	// *************************************************************************
   223  	w.borderSize = image.Pt(
   224  		windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
   225  		windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
   226  	)
   227  	//fmt.Println("w.borderSize X=", w.borderSize.X, "Y=", w.borderSize.Y, ")")
   228  	w.ProcessEvent(ConfigEvent{Config: w.config})
   229  }
   230  
   231  func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
   232  	win, exists := winMap.Load(hwnd)
   233  	if !exists {
   234  		return windows.DefWindowProc(hwnd, msg, wParam, lParam)
   235  	}
   236  
   237  	w := win.(*window)
   238  
   239  	switch msg {
   240  	case windows.WM_UNICHAR:
   241  		if wParam == windows.UNICODE_NOCHAR {
   242  			// Tell the system that we accept WM_UNICHAR messages.
   243  			return windows.TRUE
   244  		}
   245  		fallthrough
   246  	case windows.WM_CHAR:
   247  		if r := rune(wParam); unicode.IsPrint(r) {
   248  			w.w.EditorInsert(string(r))
   249  		}
   250  		// The message is processed.
   251  		return windows.TRUE
   252  	case windows.WM_DPICHANGED:
   253  		// Let Windows know we're prepared for runtime DPI changes.
   254  		return windows.TRUE
   255  	case windows.WM_ERASEBKGND:
   256  		// Avoid flickering between GPU content and background color.
   257  		return windows.TRUE
   258  	case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
   259  		if n, ok := convertKeyCode(wParam); ok {
   260  			e := key.Event{
   261  				Name:      n,
   262  				Modifiers: getModifiers(),
   263  				State:     key.Press,
   264  			}
   265  			if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
   266  				e.State = key.Release
   267  			}
   268  
   269  			w.ProcessEvent(e)
   270  
   271  			if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
   272  				// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
   273  				// such as cmd.exe and graphical debuggers also reserve F10.
   274  				return 0
   275  			}
   276  		}
   277  	case windows.WM_LBUTTONDOWN:
   278  		w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
   279  	case windows.WM_LBUTTONUP:
   280  		w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
   281  	case windows.WM_RBUTTONDOWN:
   282  		w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
   283  	case windows.WM_RBUTTONUP:
   284  		w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
   285  	case windows.WM_MBUTTONDOWN:
   286  		w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
   287  	case windows.WM_MBUTTONUP:
   288  		w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
   289  	case windows.WM_CANCELMODE:
   290  		w.ProcessEvent(pointer.Event{
   291  			Kind: pointer.Cancel,
   292  		})
   293  	case windows.WM_SETFOCUS:
   294  		w.config.Focused = true
   295  		w.ProcessEvent(ConfigEvent{Config: w.config})
   296  	case windows.WM_KILLFOCUS:
   297  		w.config.Focused = false
   298  		w.ProcessEvent(ConfigEvent{Config: w.config})
   299  	case windows.WM_NCHITTEST:
   300  		if w.config.Decorated {
   301  			// Let the system handle it.
   302  			break
   303  		}
   304  		x, y := coordsFromlParam(lParam)
   305  		np := windows.Point{X: int32(x), Y: int32(y)}
   306  		windows.ScreenToClient(w.hwnd, &np)
   307  		return w.hitTest(int(np.X), int(np.Y))
   308  	case windows.WM_MOUSEMOVE:
   309  		x, y := coordsFromlParam(lParam)
   310  		p := f32.Point{X: float32(x), Y: float32(y)}
   311  		w.ProcessEvent(pointer.Event{
   312  			Kind:      pointer.Move,
   313  			Source:    pointer.Mouse,
   314  			Position:  p,
   315  			Buttons:   w.pointerBtns,
   316  			Time:      windows.GetMessageTime(),
   317  			Modifiers: getModifiers(),
   318  		})
   319  	case windows.WM_MOUSEWHEEL:
   320  		w.scrollEvent(wParam, lParam, false, getModifiers())
   321  	case windows.WM_MOUSEHWHEEL:
   322  		w.scrollEvent(wParam, lParam, true, getModifiers())
   323  	case windows.WM_DESTROY:
   324  		w.ProcessEvent(Win32ViewEvent{})
   325  		w.ProcessEvent(DestroyEvent{})
   326  		if w.hdc != 0 {
   327  			windows.ReleaseDC(w.hdc)
   328  			w.hdc = 0
   329  		}
   330  		w.invMu.Lock()
   331  		// The system destroys the HWND for us.
   332  		w.hwnd = 0
   333  		w.invMu.Unlock()
   334  		windows.PostQuitMessage(0)
   335  // **************************************************************************
   336  // ******** RNW Added WM_MOVE to os.windows::windowProc 26.02.2024 **********
   337  // Updates position of the window after user interaction.
   338  	case windows.WM_MOVE:
   339  		w.update()
   340  		w.draw(true)	// DANGER! force window redraw in between move events 
   341  // **************************************************************************
   342  	case windows.WM_NCCALCSIZE:
   343  		if w.config.Decorated {
   344  			// Let Windows handle decorations.
   345  			break
   346  		}
   347  		// No client areas; we draw decorations ourselves.
   348  		if wParam != 1 {
   349  			return 0
   350  		}
   351  		// lParam contains an NCCALCSIZE_PARAMS for us to adjust.
   352  		place := windows.GetWindowPlacement(w.hwnd)
   353  		if !place.IsMaximized() {
   354  			// Nothing do adjust.
   355  			return 0
   356  		}
   357  		// Adjust window position to avoid the extra padding in maximized
   358  		// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
   359  		// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
   360  		szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
   361  		mi := windows.GetMonitorInfo(w.hwnd)
   362  		szp.Rgrc[0] = mi.WorkArea
   363  		return 0
   364  	case windows.WM_PAINT:
   365  		w.draw(true)
   366  	case windows.WM_SIZE:
   367  		w.update()
   368  		switch wParam {
   369  		case windows.SIZE_MINIMIZED:
   370  			w.config.Mode = Minimized
   371  		case windows.SIZE_MAXIMIZED:
   372  			w.config.Mode = Maximized
   373  		case windows.SIZE_RESTORED:
   374  			if w.config.Mode != Fullscreen {
   375  				w.config.Mode = Windowed
   376  			}
   377  		}
   378  	case windows.WM_GETMINMAXINFO:
   379  		mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
   380  		var bw, bh int32
   381  		if w.config.Decorated {
   382  			r := windows.GetWindowRect(w.hwnd)
   383  			cr := windows.GetClientRect(w.hwnd)
   384  			bw = r.Right - r.Left - (cr.Right - cr.Left)
   385  			bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
   386  		}
   387  		if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
   388  			mm.PtMinTrackSize = windows.Point{
   389  				X: int32(p.X) + bw,
   390  				Y: int32(p.Y) + bh,
   391  			}
   392  		}
   393  		if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
   394  			mm.PtMaxTrackSize = windows.Point{
   395  				X: int32(p.X) + bw,
   396  				Y: int32(p.Y) + bh,
   397  			}
   398  		}
   399  		return 0
   400  	case windows.WM_SETCURSOR:
   401  		w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
   402  		if w.cursorIn {
   403  			windows.SetCursor(w.cursor)
   404  			return windows.TRUE
   405  		}
   406  	case _WM_WAKEUP:
   407  		w.loop.Wakeup()
   408  		w.loop.FlushEvents()
   409  	case windows.WM_IME_STARTCOMPOSITION:
   410  		imc := windows.ImmGetContext(w.hwnd)
   411  		if imc == 0 {
   412  			return windows.TRUE
   413  		}
   414  		defer windows.ImmReleaseContext(w.hwnd, imc)
   415  		sel := w.w.EditorState().Selection
   416  		caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent)))
   417  		icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
   418  		windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
   419  		windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
   420  	case windows.WM_IME_COMPOSITION:
   421  		imc := windows.ImmGetContext(w.hwnd)
   422  		if imc == 0 {
   423  			return windows.TRUE
   424  		}
   425  		defer windows.ImmReleaseContext(w.hwnd, imc)
   426  		state := w.w.EditorState()
   427  		rng := state.compose
   428  		if rng.Start == -1 {
   429  			rng = state.Selection.Range
   430  		}
   431  		if rng.Start > rng.End {
   432  			rng.Start, rng.End = rng.End, rng.Start
   433  		}
   434  		var replacement string
   435  		switch {
   436  		case lParam&windows.GCS_RESULTSTR != 0:
   437  			replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR)
   438  		case lParam&windows.GCS_COMPSTR != 0:
   439  			replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR)
   440  		}
   441  		end := rng.Start + utf8.RuneCountInString(replacement)
   442  		w.w.EditorReplace(rng, replacement)
   443  		state = w.w.EditorState()
   444  		comp := key.Range{
   445  			Start: rng.Start,
   446  			End:   end,
   447  		}
   448  		if lParam&windows.GCS_DELTASTART != 0 {
   449  			start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART)
   450  			comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start)
   451  		}
   452  		w.w.SetComposingRegion(comp)
   453  		pos := end
   454  		if lParam&windows.GCS_CURSORPOS != 0 {
   455  			rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS)
   456  			pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel)
   457  		}
   458  		w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
   459  		return windows.TRUE
   460  	case windows.WM_IME_ENDCOMPOSITION:
   461  		w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
   462  		return windows.TRUE
   463  	}
   464  
   465  	return windows.DefWindowProc(hwnd, msg, wParam, lParam)
   466  }
   467  
   468  func getModifiers() key.Modifiers {
   469  	var kmods key.Modifiers
   470  	if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
   471  		kmods |= key.ModSuper
   472  	}
   473  	if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
   474  		kmods |= key.ModAlt
   475  	}
   476  	if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
   477  		kmods |= key.ModCtrl
   478  	}
   479  	if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
   480  		kmods |= key.ModShift
   481  	}
   482  	return kmods
   483  }
   484  
   485  // hitTest returns the non-client area hit by the point, needed to
   486  // process WM_NCHITTEST.
   487  func (w *window) hitTest(x, y int) uintptr {
   488  	if w.config.Mode == Fullscreen {
   489  		return windows.HTCLIENT
   490  	}
   491  	if w.config.Mode != Windowed {
   492  		// Only windowed mode should allow resizing.
   493  		return windows.HTCLIENT
   494  	}
   495  	// Check for resize handle before system actions; otherwise it can be impossible to
   496  	// resize a custom-decorations window when the system move area is flush with the
   497  	// edge of the window.
   498  	top := y <= w.borderSize.Y
   499  	bottom := y >= w.config.Size.Y-w.borderSize.Y
   500  	left := x <= w.borderSize.X
   501  	right := x >= w.config.Size.X-w.borderSize.X
   502  	switch {
   503  	case top && left:
   504  		return windows.HTTOPLEFT
   505  	case top && right:
   506  		return windows.HTTOPRIGHT
   507  	case bottom && left:
   508  		return windows.HTBOTTOMLEFT
   509  	case bottom && right:
   510  		return windows.HTBOTTOMRIGHT
   511  	case top:
   512  		return windows.HTTOP
   513  	case bottom:
   514  		return windows.HTBOTTOM
   515  	case left:
   516  		return windows.HTLEFT
   517  	case right:
   518  		return windows.HTRIGHT
   519  	}
   520  	p := f32.Pt(float32(x), float32(y))
   521  	if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
   522  		return windows.HTCAPTION
   523  	}
   524  	return windows.HTCLIENT
   525  }
   526  
   527  func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
   528  	if !w.config.Focused {
   529  		windows.SetFocus(w.hwnd)
   530  	}
   531  
   532  	var kind pointer.Kind
   533  	if press {
   534  		kind = pointer.Press
   535  		if w.pointerBtns == 0 {
   536  			windows.SetCapture(w.hwnd)
   537  		}
   538  		w.pointerBtns |= btn
   539  	} else {
   540  		kind = pointer.Release
   541  		w.pointerBtns &^= btn
   542  		if w.pointerBtns == 0 {
   543  			windows.ReleaseCapture()
   544  		}
   545  	}
   546  	x, y := coordsFromlParam(lParam)
   547  	p := f32.Point{X: float32(x), Y: float32(y)}
   548  	w.ProcessEvent(pointer.Event{
   549  		Kind:      kind,
   550  		Source:    pointer.Mouse,
   551  		Position:  p,
   552  		Buttons:   w.pointerBtns,
   553  		Time:      windows.GetMessageTime(),
   554  		Modifiers: kmods,
   555  	})
   556  }
   557  
   558  func coordsFromlParam(lParam uintptr) (int, int) {
   559  	x := int(int16(lParam & 0xffff))
   560  	y := int(int16((lParam >> 16) & 0xffff))
   561  	return x, y
   562  }
   563  
   564  func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
   565  	x, y := coordsFromlParam(lParam)
   566  	// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
   567  	// to other mouse events.
   568  	np := windows.Point{X: int32(x), Y: int32(y)}
   569  	windows.ScreenToClient(w.hwnd, &np)
   570  	p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
   571  	dist := float32(int16(wParam >> 16))
   572  	var sp f32.Point
   573  	if horizontal {
   574  		sp.X = dist
   575  	} else {
   576  		// support horizontal scroll (shift + mousewheel)
   577  		if kmods == key.ModShift {
   578  			sp.X = -dist
   579  		} else {
   580  			sp.Y = -dist
   581  		}
   582  	}
   583  	w.ProcessEvent(pointer.Event{
   584  		Kind:      pointer.Scroll,
   585  		Source:    pointer.Mouse,
   586  		Position:  p,
   587  		Buttons:   w.pointerBtns,
   588  		Scroll:    sp,
   589  		Modifiers: kmods,
   590  		Time:      windows.GetMessageTime(),
   591  	})
   592  }
   593  
   594  // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
   595  func (w *window) runLoop() {
   596  	msg := new(windows.Msg)
   597  loop:
   598  	for {
   599  		anim := w.animating
   600  		if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
   601  			w.draw(false)
   602  			continue
   603  		}
   604  		switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
   605  		case -1:
   606  			panic(errors.New("GetMessage failed"))
   607  		case 0:
   608  			// WM_QUIT received.
   609  			break loop
   610  		}
   611  		windows.TranslateMessage(msg)
   612  		windows.DispatchMessage(msg)
   613  	}
   614  }
   615  
   616  func (w *window) EditorStateChanged(old, new editorState) {
   617  	imc := windows.ImmGetContext(w.hwnd)
   618  	if imc == 0 {
   619  		return
   620  	}
   621  	defer windows.ImmReleaseContext(w.hwnd, imc)
   622  	if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
   623  		windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
   624  	}
   625  }
   626  
   627  func (w *window) SetAnimating(anim bool) {
   628  	w.animating = anim
   629  }
   630  
   631  func (w *window) ProcessEvent(e event.Event) {
   632  	w.w.ProcessEvent(e)
   633  	w.loop.FlushEvents()
   634  }
   635  
   636  func (w *window) Event() event.Event {
   637  	return w.loop.Event()
   638  }
   639  
   640  func (w *window) Invalidate() {
   641  	w.loop.Invalidate()
   642  }
   643  
   644  func (w *window) Run(f func()) {
   645  	w.loop.Run(f)
   646  }
   647  
   648  func (w *window) Frame(frame *op.Ops) {
   649  	w.loop.Frame(frame)
   650  }
   651  
   652  func (w *window) wakeup() {
   653  	w.invMu.Lock()
   654  	defer w.invMu.Unlock()
   655  	if w.hwnd == 0 {
   656  		w.loop.Wakeup()
   657  		w.loop.FlushEvents()
   658  		return
   659  	}
   660  	if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
   661  		panic(err)
   662  	}
   663  }
   664  
   665  func (w *window) draw(sync bool) {
   666  	if w.config.Size.X == 0 || w.config.Size.Y == 0 {
   667  		return
   668  	}
   669  	dpi := windows.GetWindowDPI(w.hwnd)
   670  	cfg := configForDPI(dpi)
   671  	w.ProcessEvent(frameEvent{
   672  		FrameEvent: FrameEvent{
   673  			Now:    time.Now(),
   674  			Size:   w.config.Size,
   675  			Metric: cfg,
   676  		},
   677  		Sync: sync,
   678  	})
   679  }
   680  
   681  func (w *window) NewContext() (context, error) {
   682  	sort.Slice(drivers, func(i, j int) bool {
   683  		return drivers[i].priority < drivers[j].priority
   684  	})
   685  	var errs []string
   686  	for _, b := range drivers {
   687  		ctx, err := b.initializer(w)
   688  		if err == nil {
   689  			return ctx, nil
   690  		}
   691  		errs = append(errs, err.Error())
   692  	}
   693  	if len(errs) > 0 {
   694  		return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
   695  	}
   696  	return nil, errors.New("NewContext: no available GPU drivers")
   697  }
   698  
   699  func (w *window) ReadClipboard() {
   700  	w.readClipboard()
   701  }
   702  
   703  func (w *window) readClipboard() error {
   704  	if err := windows.OpenClipboard(w.hwnd); err != nil {
   705  		return err
   706  	}
   707  	defer windows.CloseClipboard()
   708  	mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
   709  	if err != nil {
   710  		return err
   711  	}
   712  	ptr, err := windows.GlobalLock(mem)
   713  	if err != nil {
   714  		return err
   715  	}
   716  	defer windows.GlobalUnlock(mem)
   717  	content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
   718  	w.ProcessEvent(transfer.DataEvent{
   719  		Type: "application/text",
   720  		Open: func() io.ReadCloser {
   721  			return io.NopCloser(strings.NewReader(content))
   722  		},
   723  	})
   724  	return nil
   725  }
   726  
   727  func (w *window) Configure(options []Option) {
   728  	dpi := windows.GetSystemDPI()
   729  	metric := configForDPI(dpi)
   730  	w.config.apply(metric, options)
   731  	windows.SetWindowText(w.hwnd, w.config.Title)
   732  
   733  	style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
   734  	var showMode int32
   735  	var x, y, width, height int32
   736  	swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
   737  	winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
   738  	style &^= winStyle
   739  	switch w.config.Mode {
   740  	case Minimized:
   741  		style |= winStyle
   742  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   743  		showMode = windows.SW_SHOWMINIMIZED
   744  
   745  	case Maximized:
   746  		style |= winStyle
   747  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   748  		showMode = windows.SW_SHOWMAXIMIZED
   749  	case Windowed:
   750  		style |= winStyle
   751  		showMode = windows.SW_SHOWNORMAL
   752  		// *******************************************************************
   753  		// RNW Added Get target position for client area position.
   754  		x = int32(w.config.Pos.X) // ******** RNW Added Pos (image.Point) to config 01.11.2023 *********
   755  		y = int32(w.config.Pos.Y) // ******** RNW Added Pos (image.Point) to config 01.11.2023 *********
   756  		// *******************************************************************
   757  
   758  		// Get target for client area size.
   759  		width = int32(w.config.Size.X)
   760  		height = int32(w.config.Size.Y)
   761  		// Get the current window size and position.
   762  		wr := windows.GetWindowRect(w.hwnd)
   763  		// *******************************************************************
   764  		// ******** RNW Added Pos (image.Point) to config 01.11.2023 *********
   765  		if x < -9999 {x = wr.Left} 	//int32(-w.borderSize.X) {x = wr.Left} 
   766  		if y < -9999 {y = wr.Top}  
   767  		// *******************************************************************
   768  		if w.config.Decorated {
   769  			// Compute client size and position. Note that the client size is
   770  			// equal to the window size when we are in control of decorations.
   771  			r := windows.Rect{
   772  				Right:  width,
   773  				Bottom: height,
   774  			}
   775  			windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
   776  			width = r.Right - r.Left
   777  			height = r.Bottom - r.Top
   778  		}
   779  		if !w.config.Decorated {
   780  			// Enable drop shadows when we draw decorations.
   781  			windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
   782  		}
   783  
   784  	case Fullscreen:
   785  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   786  		mi := windows.GetMonitorInfo(w.hwnd)
   787  		x, y = mi.Monitor.Left, mi.Monitor.Top
   788  		width = mi.Monitor.Right - mi.Monitor.Left
   789  		height = mi.Monitor.Bottom - mi.Monitor.Top
   790  		showMode = windows.SW_SHOWMAXIMIZED
   791  	}
   792  	windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
   793  	windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
   794  	windows.ShowWindow(w.hwnd, showMode)
   795  	w.update()
   796  }
   797  
   798  func (w *window) WriteClipboard(mime string, s []byte) {
   799  	w.writeClipboard(string(s))
   800  }
   801  
   802  func (w *window) writeClipboard(s string) error {
   803  	if err := windows.OpenClipboard(w.hwnd); err != nil {
   804  		return err
   805  	}
   806  	defer windows.CloseClipboard()
   807  	if err := windows.EmptyClipboard(); err != nil {
   808  		return err
   809  	}
   810  	u16, err := syscall.UTF16FromString(s)
   811  	if err != nil {
   812  		return err
   813  	}
   814  	n := len(u16) * int(unsafe.Sizeof(u16[0]))
   815  	mem, err := windows.GlobalAlloc(n)
   816  	if err != nil {
   817  		return err
   818  	}
   819  	ptr, err := windows.GlobalLock(mem)
   820  	if err != nil {
   821  		windows.GlobalFree(mem)
   822  		return err
   823  	}
   824  	u16v := unsafe.Slice((*uint16)(ptr), len(u16))
   825  	copy(u16v, u16)
   826  	windows.GlobalUnlock(mem)
   827  	if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
   828  		windows.GlobalFree(mem)
   829  		return err
   830  	}
   831  	return nil
   832  }
   833  
   834  func (w *window) SetCursor(cursor pointer.Cursor) {
   835  	c, err := loadCursor(cursor)
   836  	if err != nil {
   837  		c = resources.cursor
   838  	}
   839  	w.cursor = c
   840  	if w.cursorIn {
   841  		windows.SetCursor(w.cursor)
   842  	}
   843  }
   844  
   845  // windowsCursor contains mapping from pointer.Cursor to an IDC.
   846  var windowsCursor = [...]uint16{
   847  	pointer.CursorDefault:                  windows.IDC_ARROW,
   848  	pointer.CursorNone:                     0,
   849  	pointer.CursorText:                     windows.IDC_IBEAM,
   850  	pointer.CursorVerticalText:             windows.IDC_IBEAM,
   851  	pointer.CursorPointer:                  windows.IDC_HAND,
   852  	pointer.CursorCrosshair:                windows.IDC_CROSS,
   853  	pointer.CursorAllScroll:                windows.IDC_SIZEALL,
   854  	pointer.CursorColResize:                windows.IDC_SIZEWE,
   855  	pointer.CursorRowResize:                windows.IDC_SIZENS,
   856  	pointer.CursorGrab:                     windows.IDC_SIZEALL,
   857  	pointer.CursorGrabbing:                 windows.IDC_SIZEALL,
   858  	pointer.CursorNotAllowed:               windows.IDC_NO,
   859  	pointer.CursorWait:                     windows.IDC_WAIT,
   860  	pointer.CursorProgress:                 windows.IDC_APPSTARTING,
   861  	pointer.CursorNorthWestResize:          windows.IDC_SIZENWSE,
   862  	pointer.CursorNorthEastResize:          windows.IDC_SIZENESW,
   863  	pointer.CursorSouthWestResize:          windows.IDC_SIZENESW,
   864  	pointer.CursorSouthEastResize:          windows.IDC_SIZENWSE,
   865  	pointer.CursorNorthSouthResize:         windows.IDC_SIZENS,
   866  	pointer.CursorEastWestResize:           windows.IDC_SIZEWE,
   867  	pointer.CursorWestResize:               windows.IDC_SIZEWE,
   868  	pointer.CursorEastResize:               windows.IDC_SIZEWE,
   869  	pointer.CursorNorthResize:              windows.IDC_SIZENS,
   870  	pointer.CursorSouthResize:              windows.IDC_SIZENS,
   871  	pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW,
   872  	pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE,
   873  }
   874  
   875  func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) {
   876  	switch cursor {
   877  	case pointer.CursorDefault:
   878  		return resources.cursor, nil
   879  	case pointer.CursorNone:
   880  		return 0, nil
   881  	default:
   882  		return windows.LoadCursor(windowsCursor[cursor])
   883  	}
   884  }
   885  
   886  func (w *window) ShowTextInput(show bool) {}
   887  
   888  func (w *window) SetInputHint(_ key.InputHint) {}
   889  
   890  func (w *window) HDC() syscall.Handle {
   891  	return w.hdc
   892  }
   893  
   894  func (w *window) HWND() (syscall.Handle, int, int) {
   895  	return w.hwnd, w.config.Size.X, w.config.Size.Y
   896  }
   897  
   898  func (w *window) Perform(acts system.Action) {
   899  	walkActions(acts, func(a system.Action) {
   900  		switch a {
   901  		case system.ActionCenter:
   902  			if w.config.Mode != Windowed {
   903  				break
   904  			}
   905  			r := windows.GetWindowRect(w.hwnd)
   906  			dx := r.Right - r.Left
   907  			dy := r.Bottom - r.Top
   908  			// Calculate center position on current monitor.
   909  			mi := windows.GetMonitorInfo(w.hwnd).Monitor
   910  			x := (mi.Right - mi.Left - dx) / 2
   911  			y := (mi.Bottom - mi.Top - dy) / 2
   912  			windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
   913  		case system.ActionRaise:
   914  			w.raise()
   915  		case system.ActionClose:
   916  			windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
   917  		}
   918  	})
   919  }
   920  
   921  func (w *window) raise() {
   922  	windows.SetForegroundWindow(w.hwnd)
   923  	windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
   924  		windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
   925  }
   926  
   927  func convertKeyCode(code uintptr) (key.Name, bool) {
   928  	if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
   929  		return key.Name(rune(code)), true
   930  	}
   931  	var r key.Name
   932  
   933  	switch code {
   934  	case windows.VK_ESCAPE:
   935  		r = key.NameEscape
   936  	case windows.VK_LEFT:
   937  		r = key.NameLeftArrow
   938  	case windows.VK_RIGHT:
   939  		r = key.NameRightArrow
   940  	case windows.VK_RETURN:
   941  		r = key.NameReturn
   942  	case windows.VK_UP:
   943  		r = key.NameUpArrow
   944  	case windows.VK_DOWN:
   945  		r = key.NameDownArrow
   946  	case windows.VK_HOME:
   947  		r = key.NameHome
   948  	case windows.VK_END:
   949  		r = key.NameEnd
   950  	case windows.VK_BACK:
   951  		r = key.NameDeleteBackward
   952  	case windows.VK_DELETE:
   953  		r = key.NameDeleteForward
   954  	case windows.VK_PRIOR:
   955  		r = key.NamePageUp
   956  	case windows.VK_NEXT:
   957  		r = key.NamePageDown
   958  	case windows.VK_F1:
   959  		r = key.NameF1
   960  	case windows.VK_F2:
   961  		r = key.NameF2
   962  	case windows.VK_F3:
   963  		r = key.NameF3
   964  	case windows.VK_F4:
   965  		r = key.NameF4
   966  	case windows.VK_F5:
   967  		r = key.NameF5
   968  	case windows.VK_F6:
   969  		r = key.NameF6
   970  	case windows.VK_F7:
   971  		r = key.NameF7
   972  	case windows.VK_F8:
   973  		r = key.NameF8
   974  	case windows.VK_F9:
   975  		r = key.NameF9
   976  	case windows.VK_F10:
   977  		r = key.NameF10
   978  	case windows.VK_F11:
   979  		r = key.NameF11
   980  	case windows.VK_F12:
   981  		r = key.NameF12
   982  	case windows.VK_TAB:
   983  		r = key.NameTab
   984  	case windows.VK_SPACE:
   985  		r = key.NameSpace
   986  	case windows.VK_OEM_1:
   987  		r = ";"
   988  	case windows.VK_OEM_PLUS:
   989  		r = "+"
   990  	case windows.VK_OEM_COMMA:
   991  		r = ","
   992  	case windows.VK_OEM_MINUS:
   993  		r = "-"
   994  	case windows.VK_OEM_PERIOD:
   995  		r = "."
   996  	case windows.VK_OEM_2:
   997  		r = "/"
   998  	case windows.VK_OEM_3:
   999  		r = "`"
  1000  	case windows.VK_OEM_4:
  1001  		r = "["
  1002  	case windows.VK_OEM_5, windows.VK_OEM_102:
  1003  		r = "\\"
  1004  	case windows.VK_OEM_6:
  1005  		r = "]"
  1006  	case windows.VK_OEM_7:
  1007  		r = "'"
  1008  	case windows.VK_CONTROL:
  1009  		r = key.NameCtrl
  1010  	case windows.VK_SHIFT:
  1011  		r = key.NameShift
  1012  	case windows.VK_MENU:
  1013  		r = key.NameAlt
  1014  	case windows.VK_LWIN, windows.VK_RWIN:
  1015  		r = key.NameSuper
  1016  	default:
  1017  		return "", false
  1018  	}
  1019  	return r, true
  1020  }
  1021  
  1022  func configForDPI(dpi int) unit.Metric {
  1023  	const inchPrDp = 1.0 / 96.0
  1024  	ppdp := float32(dpi) * inchPrDp
  1025  	return unit.Metric{
  1026  		PxPerDp: ppdp,
  1027  		PxPerSp: ppdp,
  1028  	}
  1029  }
  1030  
  1031  func (Win32ViewEvent) implementsViewEvent() {}
  1032  func (Win32ViewEvent) ImplementsEvent()     {}