github.com/Seikaijyu/gio@v0.0.1/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  	"runtime"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  	"unicode"
    15  	"unicode/utf8"
    16  	"unsafe"
    17  
    18  	syscall "golang.org/x/sys/windows"
    19  
    20  	"github.com/Seikaijyu/gio/app/internal/windows"
    21  	"github.com/Seikaijyu/gio/unit"
    22  	gowindows "golang.org/x/sys/windows"
    23  
    24  	"github.com/Seikaijyu/gio/f32"
    25  	"github.com/Seikaijyu/gio/io/clipboard"
    26  	"github.com/Seikaijyu/gio/io/key"
    27  	"github.com/Seikaijyu/gio/io/pointer"
    28  	"github.com/Seikaijyu/gio/io/system"
    29  )
    30  
    31  // ViewEvent 结构体包含了一个 HWND,这是一个窗口的句柄
    32  type ViewEvent struct {
    33  	HWND uintptr
    34  }
    35  
    36  // window 结构体定义了一个窗口的各种属性
    37  type window struct {
    38  	hwnd        syscall.Handle  // 窗口的句柄
    39  	hdc         syscall.Handle  // 设备上下文的句柄
    40  	w           *callbacks      // 回调函数的集合
    41  	stage       system.Stage    // 系统的阶段
    42  	pointerBtns pointer.Buttons // 指针按钮的状态
    43  
    44  	// cursorIn 标记鼠标光标是否在窗口内,根据最近的 WM_SETCURSOR 消息来判断
    45  	cursorIn bool
    46  	cursor   syscall.Handle // 光标的句柄
    47  
    48  	// placement 在全屏模式下保存上一次窗口的位置
    49  	placement *windows.WindowPlacement
    50  
    51  	animating bool // 标记窗口是否在动画中
    52  	focused   bool // 标记窗口是否被聚焦
    53  
    54  	borderSize image.Point // 窗口边框的大小
    55  	config     Config      // 窗口的配置信息
    56  }
    57  
    58  // _WM_WAKEUP 是一个自定义的 Windows 消息,用于唤醒窗口
    59  const _WM_WAKEUP = windows.WM_USER + iota
    60  
    61  // gpuAPI 结构体定义了一个 GPU API,包含了优先级和初始化函数
    62  type gpuAPI struct {
    63  	priority    int                              // 优先级
    64  	initializer func(w *window) (context, error) // 初始化函数
    65  }
    66  
    67  // drivers 是一个存放所有可能的 Context 实现的列表
    68  var drivers []gpuAPI
    69  
    70  // winMap 是一个映射,将 win32 的 HWND 映射到 *windows
    71  var winMap sync.Map
    72  
    73  // iconID 是资源文件中图标的 ID
    74  const iconID = 1
    75  
    76  // resources 结构体包含了一些资源,如模块句柄、窗口类和光标资源
    77  var resources struct {
    78  	once   sync.Once      // 用于只执行一次的同步标记
    79  	handle syscall.Handle // 从 GetModuleHandle 获取的模块句柄
    80  	class  uint16         // 从 RegisterClassEx 注册的 Gio 窗口类
    81  	cursor syscall.Handle // 光标资源的句柄
    82  }
    83  
    84  // osMain 函数是操作系统主函数,这里是一个空实现
    85  func osMain() {
    86  	select {}
    87  }
    88  
    89  // newWindow 函数用于创建一个新的窗口
    90  func newWindow(window *callbacks, options []Option) error {
    91  	// 创建一个错误通道
    92  	cerr := make(chan error)
    93  	// 在一个新的协程中创建窗口
    94  	go func() {
    95  		// GetMessage 和 PeekMessage 可以基于窗口的 HWND 进行过滤,但是
    96  		// 这样会忽略掉特定于线程的消息,如 WM_QUIT。
    97  		// 因此,我们锁定线程,让窗口消息通过未经过滤的 GetMessage 调用到达。
    98  		runtime.LockOSThread()
    99  		// 创建一个原生窗口
   100  		w, err := createNativeWindow()
   101  		// 如果创建窗口时出错,将错误发送到错误通道并返回
   102  		if err != nil {
   103  			cerr <- err
   104  			return
   105  		}
   106  		// 将 nil 错误发送到错误通道
   107  		cerr <- nil
   108  		// 将创建的窗口存储到 winMap 中
   109  		winMap.Store(w.hwnd, w)
   110  		// 在函数返回时从 winMap 中删除窗口
   111  		defer winMap.Delete(w.hwnd)
   112  		// 设置窗口的回调函数
   113  		w.w = window
   114  		// 设置窗口的驱动程序
   115  		w.w.SetDriver(w)
   116  		// 发送一个 ViewEvent 事件
   117  		w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
   118  		// 配置窗口
   119  		w.Configure(options)
   120  		// 将窗口设置为前台窗口
   121  		windows.SetForegroundWindow(w.hwnd)
   122  		// 设置窗口的焦点
   123  		windows.SetFocus(w.hwnd)
   124  		// 由于光标的窗口类是空的,
   125  		// 所以在这里设置它以显示光标。
   126  		w.SetCursor(pointer.CursorDefault)
   127  		// 进入窗口的消息循环
   128  		if err := w.loop(); err != nil {
   129  			// 如果消息循环出错,抛出 panic
   130  			panic(err)
   131  		}
   132  	}()
   133  	// 返回错误通道中的错误
   134  	return <-cerr
   135  }
   136  
   137  // initResources 函数用于初始化全局的 resources。
   138  func initResources() error {
   139  	// 设置当前进程为 DPI Aware,使得窗口在高 DPI 设置下能够正确显示
   140  	windows.SetProcessDPIAware()
   141  	// 获取当前模块的句柄
   142  	hInst, err := windows.GetModuleHandle()
   143  	if err != nil {
   144  		// 如果获取失败,返回错误
   145  		return err
   146  	}
   147  	// 将获取到的模块句柄赋值给 resources 的 handle 字段
   148  	resources.handle = hInst
   149  	// 加载系统预定义的光标
   150  	c, err := windows.LoadCursor(windows.IDC_ARROW)
   151  	if err != nil {
   152  		// 如果加载失败,返回错误
   153  		return err
   154  	}
   155  	// 将加载到的光标赋值给 resources 的 cursor 字段
   156  	resources.cursor = c
   157  	// 加载图标资源
   158  	icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
   159  	// 定义窗口类
   160  	wcls := windows.WndClassEx{
   161  		CbSize:        uint32(unsafe.Sizeof(windows.WndClassEx{})),                // 结构体的大小
   162  		Style:         windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC, // 窗口样式
   163  		LpfnWndProc:   syscall.NewCallback(windowProc),                            // 窗口过程函数
   164  		HInstance:     hInst,                                                      // 模块句柄
   165  		HIcon:         icon,                                                       // 图标
   166  		LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),                      // 窗口类名
   167  	}
   168  	// 注册窗口类
   169  	cls, err := windows.RegisterClassEx(&wcls)
   170  	if err != nil {
   171  		// 如果注册失败,返回错误
   172  		return err
   173  	}
   174  	// 将注册到的窗口类赋值给 resources 的 class 字段
   175  	resources.class = cls
   176  	// 如果所有操作都成功,返回 nil 表示没有错误
   177  	return nil
   178  }
   179  
   180  // 定义窗口的扩展样式
   181  const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
   182  
   183  // 窗口ID,默认为 0
   184  var HWND syscall.Handle = 0
   185  
   186  // createNativeWindow 函数用于创建一个本地窗口
   187  func createNativeWindow() (*window, error) {
   188  	var resErr error
   189  	// 使用 sync.Once 确保全局的 resources 只被初始化一次
   190  	resources.once.Do(func() {
   191  		resErr = initResources()
   192  	})
   193  	// 如果初始化 resources 时出错,返回错误
   194  	if resErr != nil {
   195  		return nil, resErr
   196  	}
   197  	// 定义窗口的样式
   198  	const dwStyle = windows.WS_OVERLAPPEDWINDOW
   199  
   200  	// 调用 CreateWindowEx 函数创建窗口
   201  	hwnd, err := windows.CreateWindowEx(
   202  		dwExStyle,       // 窗口的扩展样式
   203  		resources.class, // 窗口类
   204  		"",              // 窗口标题
   205  		dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN, // 窗口样式
   206  		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, // 窗口的初始位置
   207  		windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, // 窗口的初始大小
   208  		0,                // 父窗口的句柄
   209  		0,                // 菜单的句柄
   210  		resources.handle, // 应用程序实例的句柄
   211  		0)                // 创建窗口的参数
   212  	// 如果创建窗口时出错,返回错误
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	// 创建 window 结构体实例
   217  	w := &window{
   218  		hwnd: hwnd,
   219  	}
   220  
   221  	HWND = hwnd
   222  	// 获取窗口的设备上下文
   223  	w.hdc, err = windows.GetDC(hwnd)
   224  	// 如果获取设备上下文时出错,返回错误
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	// 如果所有操作都成功,返回创建的窗口
   229  	return w, nil
   230  }
   231  
   232  // 拖动处理函数
   233  var dragHandler func([]string) = func(s []string) {}
   234  
   235  // 窗口是否接受文件拖放
   236  func DragAcceptFiles(hwnd syscall.Handle, accept bool) {
   237  	windows.DragAcceptFiles(hwnd, accept)
   238  }
   239  
   240  // 自定义处理文件拖放事件
   241  func CustomDragHandler(fn func([]string)) {
   242  	dragHandler = fn
   243  }
   244  
   245  // update() 函数用于处理用户所做的更改,并更新配置。
   246  // 它会读取窗口的样式以及大小/位置,并更新 w.config。
   247  // 如果有任何更改,它会发出一个 ConfigEvent 来通知应用程序。
   248  func (w *window) update() {
   249  	// 获取窗口的客户区大小
   250  	cr := windows.GetClientRect(w.hwnd)
   251  	// 更新窗口的大小
   252  	w.config.Size = image.Point{
   253  		// 宽度为客户区的右边界减去左边界
   254  		X: int(cr.Right - cr.Left),
   255  		// 高度为客户区的下边界减去上边界
   256  		Y: int(cr.Bottom - cr.Top),
   257  	}
   258  
   259  	// 获取窗口边框的大小
   260  	w.borderSize = image.Pt(
   261  		// 边框的宽度为系统的 SM_CXSIZEFRAME 参数
   262  		windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
   263  		// 边框的高度为系统的 SM_CYSIZEFRAME 参数
   264  		windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
   265  	)
   266  	// 发出 ConfigEvent 事件,通知应用程序窗口的配置已经更改
   267  	w.w.Event(ConfigEvent{Config: w.config})
   268  }
   269  
   270  // windowProc 函数是窗口过程函数,用于处理窗口接收到的消息。
   271  // 它接收四个参数:窗口句柄、消息、以及两个消息参数。
   272  func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
   273  	// 从 winMap 中获取窗口句柄对应的窗口
   274  	// winMap 是一个 map,键为窗口句柄,值为窗口对象
   275  	win, exists := winMap.Load(hwnd)
   276  	// 如果 winMap 中不存在该窗口句柄,说明这是一个未注册的窗口
   277  	// 对于未注册的窗口,我们直接调用 DefWindowProc 函数进行默认处理
   278  	if !exists {
   279  		return windows.DefWindowProc(hwnd, msg, wParam, lParam)
   280  	}
   281  
   282  	// 如果 winMap 中存在该窗口句柄,我们将其转换为 *window 类型
   283  	// 并赋值给 w,以便后续处理
   284  	w := win.(*window)
   285  	// 根据消息的类型进行不同的处理
   286  	switch msg {
   287  	case windows.WM_DROPFILES:
   288  		files := windows.DragFiles(uintptr(wParam))
   289  		if files != nil {
   290  			// 如果接收到的是 WM_DROPFILES 消息,处理文件拖放事件
   291  			dragHandler(files)
   292  		}
   293  		// 释放拖放操作
   294  		windows.DragFinish(wParam)
   295  		// 消息已经被处理
   296  		return windows.TRUE
   297  	case windows.WM_UNICHAR:
   298  		// 如果接收到的是 WM_UNICHAR 消息
   299  		if wParam == windows.UNICODE_NOCHAR {
   300  			// 如果参数表示没有字符,那么告诉系统我们接受 WM_UNICHAR 消息
   301  			return windows.TRUE
   302  		}
   303  		fallthrough
   304  	case windows.WM_CHAR:
   305  		// 如果接收到的是 WM_CHAR 消息
   306  		if r := rune(wParam); unicode.IsPrint(r) {
   307  			// 如果参数是可打印的字符,那么在编辑器中插入该字符
   308  			w.w.EditorInsert(string(r))
   309  		}
   310  		// 消息已经被处理
   311  		return windows.TRUE
   312  	case windows.WM_DPICHANGED:
   313  		// 如果接收到的是 WM_DPICHANGED 消息,告诉 Windows 我们已经准备好进行运行时 DPI 的改变
   314  		return windows.TRUE
   315  	case windows.WM_ERASEBKGND:
   316  		// 如果接收到的是 WM_ERASEBKGND 消息,为了避免 GPU 内容和背景颜色之间的闪烁,返回 TRUE
   317  		return windows.TRUE
   318  	case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
   319  		// 如果接收到的是键盘按下或释放的消息
   320  		if n, ok := convertKeyCode(wParam); ok {
   321  			// 如果参数是有效的键码,那么创建一个键盘事件
   322  			e := key.Event{
   323  				Name:      n,
   324  				Modifiers: getModifiers(),
   325  				State:     key.Press,
   326  			}
   327  			// 如果消息是键盘释放,那么修改事件的状态
   328  			if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
   329  				e.State = key.Release
   330  			}
   331  
   332  			// 发出键盘事件
   333  			w.w.Event(e)
   334  
   335  			if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
   336  				// 如果按下的是 F10 键,那么保留它,不让它打开系统菜单。其他 Windows 程序
   337  				// 如 cmd.exe 和图形调试器也会保留 F10 键。
   338  				return 0
   339  			}
   340  		}
   341  	case windows.WM_LBUTTONDOWN:
   342  		// 如果接收到的是鼠标左键按下的消息,处理鼠标按键事件
   343  		w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
   344  	case windows.WM_LBUTTONUP:
   345  		// 如果接收到的是鼠标左键释放的消息,处理鼠标按键事件
   346  		w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
   347  	case windows.WM_RBUTTONDOWN:
   348  		// 如果接收到的是鼠标右键按下的消息,处理鼠标按键事件
   349  		w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
   350  	case windows.WM_RBUTTONUP:
   351  		// 如果接收到的是鼠标右键释放的消息,处理鼠标按键事件
   352  		w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
   353  	case windows.WM_MBUTTONDOWN:
   354  		// 如果接收到的是鼠标中键按下的消息,处理鼠标按键事件
   355  		w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
   356  	case windows.WM_MBUTTONUP:
   357  		// 如果接收到的是鼠标中键释放的消息,处理鼠标按键事件
   358  		w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
   359  	case windows.WM_CANCELMODE:
   360  		// 如果接收到的是 WM_CANCELMODE 消息,发出一个取消的鼠标事件
   361  		w.w.Event(pointer.Event{
   362  			Kind: pointer.Cancel,
   363  		})
   364  	case windows.WM_SETFOCUS:
   365  		// 如果接收到的是 WM_SETFOCUS 消息,设置窗口为获取焦点状态,并发出一个焦点事件
   366  		w.focused = true
   367  		w.w.Event(key.FocusEvent{Focus: true})
   368  	case windows.WM_KILLFOCUS:
   369  		// 如果接收到的是 WM_KILLFOCUS 消息,设置窗口为失去焦点状态,并发出一个焦点事件
   370  		w.focused = false
   371  		w.w.Event(key.FocusEvent{Focus: false})
   372  	case windows.WM_NCACTIVATE:
   373  		// 如果接收到的是 WM_NCACTIVATE 消息,根据 wParam 的值改变窗口的状态
   374  		if w.stage >= system.StageInactive {
   375  			if wParam == windows.TRUE {
   376  				w.setStage(system.StageRunning)
   377  			} else {
   378  				w.setStage(system.StageInactive)
   379  			}
   380  		}
   381  	case windows.WM_NCHITTEST:
   382  		// 如果接收到的是 WM_NCHITTEST 消息,如果窗口是装饰的,让系统处理它
   383  		if w.config.Decorated {
   384  			// 让系统处理它
   385  			break
   386  		}
   387  		// 否则,将 lParam 转换为坐标,并进行命中测试
   388  		x, y := coordsFromlParam(lParam)
   389  		np := windows.Point{X: int32(x), Y: int32(y)}
   390  		windows.ScreenToClient(w.hwnd, &np)
   391  		return w.hitTest(int(np.X), int(np.Y))
   392  	case windows.WM_MOUSEMOVE:
   393  		// 如果接收到的是 WM_MOUSEMOVE 消息,将 lParam 转换为坐标,并发出一个鼠标移动事件
   394  		x, y := coordsFromlParam(lParam)
   395  		p := f32.Point{X: float32(x), Y: float32(y)}
   396  		w.w.Event(pointer.Event{
   397  			Kind:      pointer.Move,
   398  			Source:    pointer.Mouse,
   399  			Position:  p,
   400  			Buttons:   w.pointerBtns,
   401  			Time:      windows.GetMessageTime(),
   402  			Modifiers: getModifiers(),
   403  		})
   404  	case windows.WM_MOUSEWHEEL:
   405  		// 如果接收到的是 WM_MOUSEWHEEL 消息,处理鼠标滚轮事件
   406  		w.scrollEvent(wParam, lParam, false, getModifiers())
   407  	case windows.WM_MOUSEHWHEEL:
   408  		// 如果接收到的是 WM_MOUSEHWHEEL 消息,处理鼠标水平滚轮事件
   409  		w.scrollEvent(wParam, lParam, true, getModifiers())
   410  	case windows.WM_DESTROY:
   411  		// 如果接收到的是 WM_DESTROY 消息,发出一个视图事件和一个销毁事件
   412  		w.w.Event(ViewEvent{})
   413  		w.w.Event(system.DestroyEvent{})
   414  		// 如果设备上下文句柄不为 0,释放它
   415  		if w.hdc != 0 {
   416  			windows.ReleaseDC(w.hdc)
   417  			w.hdc = 0
   418  		}
   419  		// 系统会为我们销毁窗口句柄
   420  		w.hwnd = 0
   421  		// 发送一个退出消息
   422  		windows.PostQuitMessage(0)
   423  	case windows.WM_NCCALCSIZE:
   424  		// 如果接收到的是 WM_NCCALCSIZE 消息,如果窗口是装饰的,让 Windows 处理装饰
   425  		if w.config.Decorated {
   426  			// 让 Windows 处理装饰
   427  			break
   428  		}
   429  		// 没有客户区域;我们自己绘制装饰
   430  		if wParam != 1 {
   431  			return 0
   432  		}
   433  		// lParam 包含一个 NCCALCSIZE_PARAMS,我们可以调整它
   434  		place := windows.GetWindowPlacement(w.hwnd)
   435  		if !place.IsMaximized() {
   436  			// 没有需要调整的
   437  			return 0
   438  		}
   439  		// 调整窗口位置以避免在最大化状态下的额外填充
   440  		// 参见 https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543
   441  		// 注意,试图在 WM_GETMINMAXINFO 中进行调整会被 Windows 忽略
   442  		szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
   443  		mi := windows.GetMonitorInfo(w.hwnd)
   444  		szp.Rgrc[0] = mi.WorkArea
   445  		return 0
   446  	case windows.WM_PAINT:
   447  		// 如果接收到的是 WM_PAINT 消息,执行绘制操作
   448  		w.draw(true)
   449  	case windows.WM_SIZE:
   450  		// 如果接收到的是 WM_SIZE 消息,更新窗口大小
   451  		w.update()
   452  		switch wParam {
   453  		case windows.SIZE_MINIMIZED:
   454  			// 如果窗口被最小化,设置窗口模式为最小化,并将窗口状态设置为暂停
   455  			w.config.Mode = Minimized
   456  			w.setStage(system.StagePaused)
   457  		case windows.SIZE_MAXIMIZED:
   458  			// 如果窗口被最大化,设置窗口模式为最大化,并将窗口状态设置为运行
   459  			w.config.Mode = Maximized
   460  			w.setStage(system.StageRunning)
   461  		case windows.SIZE_RESTORED:
   462  			// 如果窗口被恢复到正常大小,设置窗口模式为窗口化(除非当前模式是全屏),并将窗口状态设置为运行
   463  			if w.config.Mode != Fullscreen {
   464  				w.config.Mode = Windowed
   465  			}
   466  			w.setStage(system.StageRunning)
   467  		}
   468  	case windows.WM_GETMINMAXINFO:
   469  		// 如果接收到的是 WM_GETMINMAXINFO 消息,获取窗口的最小和最大尺寸信息
   470  		mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
   471  		var bw, bh int32
   472  		if w.config.Decorated {
   473  			// 如果窗口是装饰的,获取窗口和客户区的尺寸,计算边框的宽度和高度
   474  			r := windows.GetWindowRect(w.hwnd)
   475  			cr := windows.GetClientRect(w.hwnd)
   476  			bw = r.Right - r.Left - (cr.Right - cr.Left)
   477  			bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
   478  		}
   479  		if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
   480  			// 如果设置了窗口的最小尺寸,将其加上边框的尺寸,设置为窗口的最小跟踪尺寸
   481  			mm.PtMinTrackSize = windows.Point{
   482  				X: int32(p.X) + bw,
   483  				Y: int32(p.Y) + bh,
   484  			}
   485  		}
   486  		if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
   487  			// 如果设置了窗口的最大尺寸,将其加上边框的尺寸,设置为窗口的最大跟踪尺寸
   488  			mm.PtMaxTrackSize = windows.Point{
   489  				X: int32(p.X) + bw,
   490  				Y: int32(p.Y) + bh,
   491  			}
   492  		}
   493  		return 0
   494  	case windows.WM_SETCURSOR:
   495  		// 如果接收到的是 WM_SETCURSOR 消息,检查光标是否在客户区内
   496  		w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
   497  		if w.cursorIn {
   498  			// 如果光标在客户区内,设置光标
   499  			windows.SetCursor(w.cursor)
   500  			return windows.TRUE
   501  		}
   502  	case _WM_WAKEUP:
   503  		// 如果接收到的是 _WM_WAKEUP 消息,触发唤醒事件
   504  		w.w.Event(wakeupEvent{})
   505  	case windows.WM_IME_STARTCOMPOSITION:
   506  		// 如果接收到的是 WM_IME_STARTCOMPOSITION 消息,开始输入法编辑
   507  		imc := windows.ImmGetContext(w.hwnd)
   508  		if imc == 0 {
   509  			// 如果无法获取输入法上下文,返回 TRUE
   510  			return windows.TRUE
   511  		}
   512  		defer windows.ImmReleaseContext(w.hwnd, imc)
   513  		// 获取编辑器的选择状态
   514  		sel := w.w.EditorState().Selection
   515  		// 转换选择的光标位置
   516  		caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent)))
   517  		icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
   518  		// 设置输入法的组合窗口和候选窗口位置
   519  		windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
   520  		windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
   521  	case windows.WM_IME_COMPOSITION:
   522  		// 如果接收到的是 WM_IME_COMPOSITION 消息,进行输入法编辑
   523  		imc := windows.ImmGetContext(w.hwnd)
   524  		if imc == 0 {
   525  			// 如果无法获取输入法上下文,返回 TRUE
   526  			return windows.TRUE
   527  		}
   528  		defer windows.ImmReleaseContext(w.hwnd, imc)
   529  		// 获取编辑器状态
   530  		state := w.w.EditorState()
   531  		rng := state.compose
   532  		if rng.Start == -1 {
   533  			rng = state.Selection.Range
   534  		}
   535  		if rng.Start > rng.End {
   536  			rng.Start, rng.End = rng.End, rng.Start
   537  		}
   538  		var replacement string
   539  		switch {
   540  		case lParam&windows.GCS_RESULTSTR != 0:
   541  			// 如果 lParam 的 GCS_RESULTSTR 位被设置,获取输入法的结果字符串
   542  			replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR)
   543  		case lParam&windows.GCS_COMPSTR != 0:
   544  			// 如果 lParam 的 GCS_COMPSTR 位被设置,获取输入法的组合字符串
   545  			replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR)
   546  		}
   547  		// 替换编辑器的内容
   548  		end := rng.Start + utf8.RuneCountInString(replacement)
   549  		w.w.EditorReplace(rng, replacement)
   550  		state = w.w.EditorState()
   551  		comp := key.Range{
   552  			Start: rng.Start,
   553  			End:   end,
   554  		}
   555  		if lParam&windows.GCS_DELTASTART != 0 {
   556  			start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART)
   557  			comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start)
   558  		}
   559  		// 设置编辑器的组合区域
   560  		w.w.SetComposingRegion(comp)
   561  		pos := end
   562  		if lParam&windows.GCS_CURSORPOS != 0 {
   563  			rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS)
   564  			pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel)
   565  		}
   566  		// 设置编辑器的选择区域
   567  		w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
   568  		return windows.TRUE
   569  	case windows.WM_IME_ENDCOMPOSITION:
   570  		// 如果接收到的是 WM_IME_ENDCOMPOSITION 消息,结束输入法编辑
   571  		w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
   572  		return windows.TRUE
   573  	}
   574  
   575  	// 如果没有匹配的消息处理,调用默认的窗口处理函数处理消息
   576  	return windows.DefWindowProc(hwnd, msg, wParam, lParam)
   577  }
   578  
   579  // getModifiers 函数用于获取当前按下的修饰键(如Ctrl、Alt等)的状态
   580  func getModifiers() key.Modifiers {
   581  	var kmods key.Modifiers
   582  	// 检查左右 Win 键是否被按下
   583  	if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
   584  		kmods |= key.ModSuper
   585  	}
   586  	// 检查 Alt 键是否被按下
   587  	if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
   588  		kmods |= key.ModAlt
   589  	}
   590  	// 检查 Ctrl 键是否被按下
   591  	if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
   592  		kmods |= key.ModCtrl
   593  	}
   594  	// 检查 Shift 键是否被按下
   595  	if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
   596  		kmods |= key.ModShift
   597  	}
   598  	// 返回按键状态
   599  	return kmods
   600  }
   601  
   602  // hitTest 函数用于检测鼠标点击的位置是否在非客户区,
   603  // 该函数主要用于处理 WM_NCHITTEST 消息。
   604  func (w *window) hitTest(x, y int) uintptr {
   605  	// 如果窗口处于全屏模式,则所有点击都视为在客户区内
   606  	if w.config.Mode == Fullscreen {
   607  		return windows.HTCLIENT
   608  	}
   609  	// 如果窗口不处于窗口模式,则不允许调整窗口大小
   610  	if w.config.Mode != Windowed {
   611  		return windows.HTCLIENT
   612  	}
   613  	// 检查鼠标是否在窗口的边缘,用于调整窗口大小
   614  	top := y <= w.borderSize.Y
   615  	bottom := y >= w.config.Size.Y-w.borderSize.Y
   616  	left := x <= w.borderSize.X
   617  	right := x >= w.config.Size.X-w.borderSize.X
   618  	switch {
   619  	case top && left:
   620  		return windows.HTTOPLEFT
   621  	case top && right:
   622  		return windows.HTTOPRIGHT
   623  	case bottom && left:
   624  		return windows.HTBOTTOMLEFT
   625  	case bottom && right:
   626  		return windows.HTBOTTOMRIGHT
   627  	case top:
   628  		return windows.HTTOP
   629  	case bottom:
   630  		return windows.HTBOTTOM
   631  	case left:
   632  		return windows.HTLEFT
   633  	case right:
   634  		return windows.HTRIGHT
   635  	}
   636  	// 检查鼠标是否在窗口的移动区域
   637  	p := f32.Pt(float32(x), float32(y))
   638  	if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
   639  		return windows.HTCAPTION
   640  	}
   641  	// 其他情况,视为在客户区内
   642  	return windows.HTCLIENT
   643  }
   644  
   645  // pointerButton 函数处理鼠标按钮的按下和释放事件
   646  func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
   647  	// 如果窗口没有焦点,设置焦点到该窗口
   648  	if !w.focused {
   649  		windows.SetFocus(w.hwnd)
   650  	}
   651  
   652  	var kind pointer.Kind
   653  	// 如果是按下事件
   654  	if press {
   655  		kind = pointer.Press
   656  		// 如果没有其他按钮被按下,获取鼠标捕获
   657  		if w.pointerBtns == 0 {
   658  			windows.SetCapture(w.hwnd)
   659  		}
   660  		// 更新按下按钮的状态
   661  		w.pointerBtns |= btn
   662  	} else {
   663  		// 如果是释放事件
   664  		kind = pointer.Release
   665  		// 更新按下按钮的状态
   666  		w.pointerBtns &^= btn
   667  		// 如果所有按钮都已释放,释放鼠标捕获
   668  		if w.pointerBtns == 0 {
   669  			windows.ReleaseCapture()
   670  		}
   671  	}
   672  	// 从 lParam 中获取鼠标的坐标
   673  	x, y := coordsFromlParam(lParam)
   674  	p := f32.Point{X: float32(x), Y: float32(y)}
   675  	// 发送鼠标事件
   676  	w.w.Event(pointer.Event{
   677  		Kind:      kind,
   678  		Source:    pointer.Mouse,
   679  		Position:  p,
   680  		Buttons:   w.pointerBtns,
   681  		Time:      windows.GetMessageTime(),
   682  		Modifiers: kmods,
   683  	})
   684  }
   685  
   686  // coordsFromlParam 函数从 lParam 中解析出鼠标的坐标
   687  func coordsFromlParam(lParam uintptr) (int, int) {
   688  	x := int(int16(lParam & 0xffff))
   689  	y := int(int16((lParam >> 16) & 0xffff))
   690  	return x, y
   691  }
   692  
   693  // scrollEvent 函数处理鼠标滚轮事件
   694  func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
   695  	// 从 lParam 中获取鼠标的屏幕坐标
   696  	x, y := coordsFromlParam(lParam)
   697  	// 将屏幕坐标转换为客户区坐标
   698  	np := windows.Point{X: int32(x), Y: int32(y)}
   699  	windows.ScreenToClient(w.hwnd, &np)
   700  	p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
   701  	// 获取滚动的距离
   702  	dist := float32(int16(wParam >> 16))
   703  	var sp f32.Point
   704  	// 如果是水平滚动
   705  	if horizontal {
   706  		sp.X = dist
   707  	} else {
   708  		// 如果按下 Shift 键,支持水平滚动
   709  		if kmods == key.ModShift {
   710  			sp.X = -dist
   711  		} else {
   712  			// 否则为垂直滚动
   713  			sp.Y = -dist
   714  		}
   715  	}
   716  	// 发送鼠标滚轮事件
   717  	w.w.Event(pointer.Event{
   718  		Kind:      pointer.Scroll,
   719  		Source:    pointer.Mouse,
   720  		Position:  p,
   721  		Buttons:   w.pointerBtns,
   722  		Scroll:    sp,
   723  		Modifiers: kmods,
   724  		Time:      windows.GetMessageTime(),
   725  	})
   726  }
   727  
   728  // loop 函数是窗口的消息循环
   729  // 该函数参考了 https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
   730  func (w *window) loop() error {
   731  	msg := new(windows.Msg)
   732  loop:
   733  	for {
   734  		anim := w.animating
   735  		// 如果窗口正在动画中,并且没有待处理的消息,绘制窗口
   736  		if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
   737  			w.draw(false)
   738  			continue
   739  		}
   740  		// 获取消息
   741  		switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
   742  		case -1:
   743  			// 如果 GetMessage 返回 -1,表示出错
   744  			return errors.New("GetMessage failed")
   745  		case 0:
   746  			// 如果 GetMessage 返回 0,表示接收到 WM_QUIT 消息,退出消息循环
   747  			break loop
   748  		}
   749  		// 转换和分发消息
   750  		windows.TranslateMessage(msg)
   751  		windows.DispatchMessage(msg)
   752  	}
   753  	return nil
   754  }
   755  
   756  // EditorStateChanged 方法用于处理编辑器状态变化
   757  // 当编辑器的选区或者代码片段发生变化时,会取消当前的输入法编辑
   758  func (w *window) EditorStateChanged(old, new editorState) {
   759  	// 获取当前窗口的输入法上下文
   760  	imc := windows.ImmGetContext(w.hwnd)
   761  	if imc == 0 {
   762  		return
   763  	}
   764  	// 确保输入法上下文在函数返回时被释放
   765  	defer windows.ImmReleaseContext(w.hwnd, imc)
   766  	// 如果选区或者代码片段发生了变化
   767  	if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
   768  		// 取消当前的输入法编辑
   769  		windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
   770  	}
   771  }
   772  
   773  // SetAnimating 方法用于设置窗口是否处于动画状态
   774  func (w *window) SetAnimating(anim bool) {
   775  	w.animating = anim
   776  }
   777  
   778  // Wakeup 方法用于唤醒窗口
   779  // 它会向窗口发送一个 _WM_WAKEUP 消息
   780  func (w *window) Wakeup() {
   781  	if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
   782  		panic(err)
   783  	}
   784  }
   785  
   786  // setStage 方法用于设置窗口的阶段
   787  // 如果阶段发生了变化,它会发送一个 StageEvent 事件
   788  func (w *window) setStage(s system.Stage) {
   789  	if s != w.stage {
   790  		w.stage = s
   791  		w.w.Event(system.StageEvent{Stage: s})
   792  	}
   793  }
   794  
   795  // draw 方法用于绘制窗口
   796  // 如果窗口的大小为 0,它会直接返回
   797  // 否则,它会根据窗口的 DPI 创建一个配置,并发送一个 frameEvent 事件
   798  func (w *window) draw(sync bool) {
   799  	if w.config.Size.X == 0 || w.config.Size.Y == 0 {
   800  		return
   801  	}
   802  	dpi := windows.GetWindowDPI(w.hwnd)
   803  	cfg := configForDPI(dpi)
   804  	w.w.Event(frameEvent{
   805  		FrameEvent: system.FrameEvent{
   806  			Now:    time.Now(),
   807  			Size:   w.config.Size,
   808  			Metric: cfg,
   809  		},
   810  		Sync: sync,
   811  	})
   812  }
   813  
   814  // NewContext 方法用于创建一个新的上下文
   815  // 它会按照优先级顺序尝试所有的驱动程序,直到成功创建一个上下文
   816  // 如果所有的驱动程序都无法创建上下文,它会返回一个错误
   817  func (w *window) NewContext() (context, error) {
   818  	// 按照优先级对驱动程序进行排序
   819  	sort.Slice(drivers, func(i, j int) bool {
   820  		return drivers[i].priority < drivers[j].priority
   821  	})
   822  	// 用于保存每个驱动程序的错误信息
   823  	var errs []string
   824  	// 遍历所有的驱动程序
   825  	for _, b := range drivers {
   826  		// 尝试使用当前驱动程序创建上下文
   827  		ctx, err := b.initializer(w)
   828  		// 如果创建成功,返回创建的上下文
   829  		if err == nil {
   830  			return ctx, nil
   831  		}
   832  		// 如果创建失败,保存错误信息
   833  		errs = append(errs, err.Error())
   834  	}
   835  	// 如果所有的驱动程序都无法创建上下文,返回一个错误
   836  	if len(errs) > 0 {
   837  		return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
   838  	}
   839  	return nil, errors.New("NewContext: no available GPU drivers")
   840  }
   841  
   842  // ReadClipboard 方法用于读取剪贴板的内容
   843  func (w *window) ReadClipboard() {
   844  	w.readClipboard()
   845  }
   846  
   847  // readClipboard 方法用于读取剪贴板的内容
   848  // 它会打开剪贴板,获取剪贴板中的数据,然后发送一个剪贴板事件
   849  func (w *window) readClipboard() error {
   850  	// 打开剪贴板
   851  	if err := windows.OpenClipboard(w.hwnd); err != nil {
   852  		return err
   853  	}
   854  	// 确保在函数返回时关闭剪贴板
   855  	defer windows.CloseClipboard()
   856  	// 获取剪贴板中的数据
   857  	mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
   858  	if err != nil {
   859  		return err
   860  	}
   861  	// 锁定内存,获取数据的指针
   862  	ptr, err := windows.GlobalLock(mem)
   863  	if err != nil {
   864  		return err
   865  	}
   866  	// 确保在函数返回时解锁内存
   867  	defer windows.GlobalUnlock(mem)
   868  	// 将数据转换为字符串
   869  	content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
   870  	// 发送剪贴板事件
   871  	w.w.Event(clipboard.Event{Text: content})
   872  	return nil
   873  }
   874  
   875  // Configure 方法用于配置窗口
   876  // 它会根据给定的选项来设置窗口的各项参数
   877  func (w *window) Configure(options []Option) {
   878  	// 获取系统的 DPI
   879  	dpi := windows.GetSystemDPI()
   880  	// 根据 DPI 创建一个配置
   881  	metric := configForDPI(dpi)
   882  	// 应用配置
   883  	w.config.apply(metric, options)
   884  	// 设置窗口的标题
   885  	windows.SetWindowText(w.hwnd, w.config.Title)
   886  
   887  	// 获取窗口的样式
   888  	style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
   889  	var showMode int32
   890  	var x, y, width, height int32
   891  	swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
   892  	winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
   893  	style &^= winStyle
   894  	// 根据窗口的模式来设置窗口的样式和显示模式
   895  	switch w.config.Mode {
   896  	case Minimized:
   897  		style |= winStyle
   898  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   899  		showMode = windows.SW_SHOWMINIMIZED
   900  
   901  	case Maximized:
   902  		style |= winStyle
   903  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   904  		showMode = windows.SW_SHOWMAXIMIZED
   905  
   906  	case Windowed:
   907  		style |= winStyle
   908  		showMode = windows.SW_SHOWNORMAL
   909  		// 获取目标的客户区大小
   910  		width = int32(w.config.Size.X)
   911  		height = int32(w.config.Size.Y)
   912  		// 获取当前窗口的大小和位置
   913  		wr := windows.GetWindowRect(w.hwnd)
   914  		x = wr.Left
   915  		y = wr.Top
   916  		if w.config.Decorated {
   917  			// 计算客户区的大小和位置。注意,当我们控制装饰时,客户区的大小等于窗口的大小
   918  			r := windows.Rect{
   919  				Right:  width,
   920  				Bottom: height,
   921  			}
   922  			windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
   923  			width = r.Right - r.Left
   924  			height = r.Bottom - r.Top
   925  		}
   926  		if !w.config.Decorated {
   927  			// 当我们绘制装饰时,启用阴影效果
   928  			windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
   929  		}
   930  
   931  	case Fullscreen:
   932  		swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
   933  		mi := windows.GetMonitorInfo(w.hwnd)
   934  		x, y = mi.Monitor.Left, mi.Monitor.Top
   935  		width = mi.Monitor.Right - mi.Monitor.Left
   936  		height = mi.Monitor.Bottom - mi.Monitor.Top
   937  		showMode = windows.SW_SHOWMAXIMIZED
   938  	}
   939  	// 设置窗口的样式
   940  	windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
   941  	// 设置窗口的位置和大小
   942  	windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
   943  	// 显示窗口
   944  	windows.ShowWindow(w.hwnd, showMode)
   945  
   946  	// 更新窗口
   947  	w.update()
   948  }
   949  
   950  // WriteClipboard 方法将指定的字符串写入剪贴板
   951  func (w *window) WriteClipboard(s string) {
   952  	w.writeClipboard(s)
   953  }
   954  
   955  // writeClipboard 方法将指定的字符串写入剪贴板,如果出现错误则返回错误
   956  func (w *window) writeClipboard(s string) error {
   957  	// 打开剪贴板
   958  	if err := windows.OpenClipboard(w.hwnd); err != nil {
   959  		return err
   960  	}
   961  	// 确保剪贴板在函数结束后关闭
   962  	defer windows.CloseClipboard()
   963  	// 清空剪贴板
   964  	if err := windows.EmptyClipboard(); err != nil {
   965  		return err
   966  	}
   967  	// 将字符串转换为 UTF16 编码
   968  	u16, err := gowindows.UTF16FromString(s)
   969  	if err != nil {
   970  		return err
   971  	}
   972  	// 分配全局内存
   973  	n := len(u16) * int(unsafe.Sizeof(u16[0]))
   974  	mem, err := windows.GlobalAlloc(n)
   975  	if err != nil {
   976  		return err
   977  	}
   978  	// 锁定全局内存
   979  	ptr, err := windows.GlobalLock(mem)
   980  	if err != nil {
   981  		windows.GlobalFree(mem)
   982  		return err
   983  	}
   984  	// 将字符串复制到全局内存
   985  	u16v := unsafe.Slice((*uint16)(ptr), len(u16))
   986  	copy(u16v, u16)
   987  	// 解锁全局内存
   988  	windows.GlobalUnlock(mem)
   989  	// 将全局内存设置为剪贴板的数据
   990  	if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
   991  		windows.GlobalFree(mem)
   992  		return err
   993  	}
   994  	// 返回无错误
   995  	return nil
   996  }
   997  
   998  // SetCursor 方法设置窗口的光标
   999  func (w *window) SetCursor(cursor pointer.Cursor) {
  1000  	// 加载光标
  1001  	c, err := loadCursor(cursor)
  1002  	if err != nil {
  1003  		c = resources.cursor
  1004  	}
  1005  	// 设置光标
  1006  	w.cursor = c
  1007  	// 如果光标在窗口内,设置窗口的光标
  1008  	if w.cursorIn {
  1009  		windows.SetCursor(w.cursor)
  1010  	}
  1011  }
  1012  
  1013  // windowsCursor 包含从 pointer.Cursor 到 IDC 的映射
  1014  var windowsCursor = [...]uint16{
  1015  	pointer.CursorDefault:                  windows.IDC_ARROW,       // 默认光标,对应 Windows 的箭头光标
  1016  	pointer.CursorNone:                     0,                       // 无光标
  1017  	pointer.CursorText:                     windows.IDC_IBEAM,       // 文本光标,对应 Windows 的 I 形光标
  1018  	pointer.CursorVerticalText:             windows.IDC_IBEAM,       // 垂直文本光标,对应 Windows 的 I 形光标
  1019  	pointer.CursorPointer:                  windows.IDC_HAND,        // 指针光标,对应 Windows 的手形光标
  1020  	pointer.CursorCrosshair:                windows.IDC_CROSS,       // 十字光标,对应 Windows 的十字形光标
  1021  	pointer.CursorAllScroll:                windows.IDC_SIZEALL,     // 全方向滚动光标,对应 Windows 的四向箭头光标
  1022  	pointer.CursorColResize:                windows.IDC_SIZEWE,      // 列调整光标,对应 Windows 的双向箭头(左右)光标
  1023  	pointer.CursorRowResize:                windows.IDC_SIZENS,      // 行调整光标,对应 Windows 的双向箭头(上下)光标
  1024  	pointer.CursorGrab:                     windows.IDC_SIZEALL,     // 抓取光标,对应 Windows 的四向箭头光标
  1025  	pointer.CursorGrabbing:                 windows.IDC_SIZEALL,     // 正在抓取光标,对应 Windows 的四向箭头光标
  1026  	pointer.CursorNotAllowed:               windows.IDC_NO,          // 不允许光标,对应 Windows 的禁止符号光标
  1027  	pointer.CursorWait:                     windows.IDC_WAIT,        // 等待光标,对应 Windows 的等待符号光标
  1028  	pointer.CursorProgress:                 windows.IDC_APPSTARTING, // 进度光标,对应 Windows 的应用启动光标
  1029  	pointer.CursorNorthWestResize:          windows.IDC_SIZENWSE,    // 西北调整光标,对应 Windows 的双向箭头(左上右下)光标
  1030  	pointer.CursorNorthEastResize:          windows.IDC_SIZENESW,    // 东北调整光标,对应 Windows 的双向箭头(右上左下)光标
  1031  	pointer.CursorSouthWestResize:          windows.IDC_SIZENESW,    // 西南调整光标,对应 Windows 的双向箭头(右上左下)光标
  1032  	pointer.CursorSouthEastResize:          windows.IDC_SIZENWSE,    // 东南调整光标,对应 Windows 的双向箭头(左上右下)光标
  1033  	pointer.CursorNorthSouthResize:         windows.IDC_SIZENS,      // 南北调整光标,对应 Windows 的双向箭头(上下)光标
  1034  	pointer.CursorEastWestResize:           windows.IDC_SIZEWE,      // 东西调整光标,对应 Windows 的双向箭头(左右)光标
  1035  	pointer.CursorWestResize:               windows.IDC_SIZEWE,      // 西调整光标,对应 Windows 的双向箭头(左右)光标
  1036  	pointer.CursorEastResize:               windows.IDC_SIZEWE,      // 东调整光标,对应 Windows 的双向箭头(左右)光标
  1037  	pointer.CursorNorthResize:              windows.IDC_SIZENS,      // 北调整光标,对应 Windows 的双向箭头(上下)光标
  1038  	pointer.CursorSouthResize:              windows.IDC_SIZENS,      // 南调整光标,对应 Windows 的双向箭头(上下)光标
  1039  	pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW,    // 东北西南调整光标,对应 Windows 的双向箭头(右上左下)光标
  1040  	pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE,    // 西北东南调整光标,对应 Windows 的双向箭头(左上右下)光标
  1041  }
  1042  
  1043  // loadCursor 函数根据指定的光标类型加载光标,如果出现错误则返回错误
  1044  func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) {
  1045  	switch cursor {
  1046  	case pointer.CursorDefault:
  1047  		return resources.cursor, nil // 默认光标,直接返回预设的光标
  1048  	case pointer.CursorNone:
  1049  		return 0, nil // 无光标,返回0
  1050  	default:
  1051  		return windows.LoadCursor(windowsCursor[cursor]) // 其他类型的光标,通过 windows.LoadCursor 加载
  1052  	}
  1053  }
  1054  
  1055  // ShowTextInput 方法用于显示或隐藏文本输入,此处为空实现
  1056  func (w *window) ShowTextInput(show bool) {}
  1057  
  1058  // SetInputHint 方法用于设置输入提示,此处为空实现
  1059  func (w *window) SetInputHint(_ key.InputHint) {}
  1060  
  1061  // HDC 方法返回窗口的设备上下文句柄
  1062  func (w *window) HDC() syscall.Handle {
  1063  	return w.hdc
  1064  }
  1065  
  1066  // HWND 方法返回窗口的句柄以及窗口的宽度和高度
  1067  func (w *window) HWND() (syscall.Handle, int, int) {
  1068  	return w.hwnd, w.config.Size.X, w.config.Size.Y
  1069  }
  1070  
  1071  // Perform 方法用于执行一系列的系统动作
  1072  func (w *window) Perform(acts system.Action) {
  1073  	walkActions(acts, func(a system.Action) {
  1074  		switch a {
  1075  		case system.ActionCenter: // 窗口居中动作
  1076  			if w.config.Mode != Windowed {
  1077  				break
  1078  			}
  1079  			r := windows.GetWindowRect(w.hwnd) // 获取窗口的矩形区域
  1080  			dx := r.Right - r.Left             // 计算窗口的宽度
  1081  			dy := r.Bottom - r.Top             // 计算窗口的高度
  1082  			// Calculate center position on current monitor.
  1083  			mi := windows.GetMonitorInfo(w.hwnd).Monitor // 获取当前显示器的信息
  1084  			x := (mi.Right - mi.Left - dx) / 2           // 计算窗口在显示器上居中的横坐标
  1085  			y := (mi.Bottom - mi.Top - dy) / 2           // 计算窗口在显示器上居中的纵坐标
  1086  			// 设置窗口的位置和大小,使其居中
  1087  			windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
  1088  		case system.ActionRaise: // 窗口置顶动作
  1089  			w.raise()
  1090  		case system.ActionClose: // 关闭窗口动作
  1091  			windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
  1092  		}
  1093  	})
  1094  }
  1095  
  1096  // raise 方法用于将窗口置顶
  1097  func (w *window) raise() {
  1098  	windows.SetForegroundWindow(w.hwnd) // 将窗口设置为前台窗口
  1099  	// 将窗口置顶,但不改变其位置和大小
  1100  	windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
  1101  		windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
  1102  }
  1103  
  1104  // convertKeyCode 函数用于将虚拟键码转换为字符串
  1105  func convertKeyCode(code uintptr) (string, bool) {
  1106  	if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
  1107  		return string(rune(code)), true // 如果虚拟键码是数字或字母,则直接转换为字符串
  1108  	}
  1109  	var r string
  1110  	// 根据虚拟键码的值,将其转换为对应的键名
  1111  	switch code {
  1112  	case windows.VK_ESCAPE:
  1113  		r = key.NameEscape // Escape 键
  1114  	case windows.VK_LEFT:
  1115  		r = key.NameLeftArrow // 左箭头键
  1116  	case windows.VK_RIGHT:
  1117  		r = key.NameRightArrow // 右箭头键
  1118  	case windows.VK_RETURN:
  1119  		r = key.NameReturn // 回车键
  1120  	case windows.VK_UP:
  1121  		r = key.NameUpArrow // 上箭头键
  1122  	case windows.VK_DOWN:
  1123  		r = key.NameDownArrow // 下箭头键
  1124  	case windows.VK_HOME:
  1125  		r = key.NameHome // Home 键
  1126  	case windows.VK_END:
  1127  		r = key.NameEnd // End 键
  1128  	case windows.VK_BACK:
  1129  		r = key.NameDeleteBackward // Backspace 键
  1130  	case windows.VK_DELETE:
  1131  		r = key.NameDeleteForward // Delete 键
  1132  	case windows.VK_PRIOR:
  1133  		r = key.NamePageUp // Page Up 键
  1134  	case windows.VK_NEXT:
  1135  		r = key.NamePageDown // Page Down 键
  1136  	case windows.VK_F1:
  1137  		r = key.NameF1 // F1 键
  1138  	case windows.VK_F2:
  1139  		r = key.NameF2 // F2 键
  1140  	case windows.VK_F3:
  1141  		r = key.NameF3 // F3 键
  1142  	case windows.VK_F4:
  1143  		r = key.NameF4 // F4 键
  1144  	case windows.VK_F5:
  1145  		r = key.NameF5 // F5 键
  1146  	case windows.VK_F6:
  1147  		r = key.NameF6 // F6 键
  1148  	case windows.VK_F7:
  1149  		r = key.NameF7 // F7 键
  1150  	case windows.VK_F8:
  1151  		r = key.NameF8 // F8 键
  1152  	case windows.VK_F9:
  1153  		r = key.NameF9 // F9 键
  1154  	case windows.VK_F10:
  1155  		r = key.NameF10 // F10 键
  1156  	case windows.VK_F11:
  1157  		r = key.NameF11 // F11 键
  1158  	case windows.VK_F12:
  1159  		r = key.NameF12 // F12 键
  1160  	case windows.VK_TAB:
  1161  		r = key.NameTab // Tab 键
  1162  	case windows.VK_SPACE:
  1163  		r = key.NameSpace // 空格键
  1164  	case windows.VK_OEM_1:
  1165  		r = ";" // 分号键
  1166  	case windows.VK_OEM_PLUS:
  1167  		r = "+" // 加号键
  1168  	case windows.VK_OEM_COMMA:
  1169  		r = "," // 逗号键
  1170  	case windows.VK_OEM_MINUS:
  1171  		r = "-" // 减号键
  1172  	case windows.VK_OEM_PERIOD:
  1173  		r = "." // 句号键
  1174  	case windows.VK_OEM_2:
  1175  		r = "/" // 斜杠键
  1176  	case windows.VK_OEM_3:
  1177  		r = "`" // 反引号键
  1178  	case windows.VK_OEM_4:
  1179  		r = "[" // 左方括号键
  1180  	case windows.VK_OEM_5, windows.VK_OEM_102:
  1181  		r = "\\" // 反斜杠键
  1182  	case windows.VK_OEM_6:
  1183  		r = "]" // 右方括号键
  1184  	case windows.VK_OEM_7:
  1185  		r = "'" // 单引号键
  1186  	case windows.VK_CONTROL:
  1187  		r = key.NameCtrl // Ctrl 键
  1188  	case windows.VK_SHIFT:
  1189  		r = key.NameShift // Shift 键
  1190  	case windows.VK_MENU:
  1191  		r = key.NameAlt // Alt 键
  1192  	case windows.VK_LWIN, windows.VK_RWIN:
  1193  		r = key.NameSuper // Win 键
  1194  	default:
  1195  		return "", false // 如果没有匹配的键名,返回空字符串和 false
  1196  	}
  1197  	return r, true // 返回键名和 true
  1198  }
  1199  
  1200  // configForDPI 函数根据给定的 DPI(每英寸点数)值生成一个 unit.Metric 对象
  1201  // 这个对象包含了每个设备独立像素(DP)和比例独立像素(SP)的像素数
  1202  func configForDPI(dpi int) unit.Metric {
  1203  	// 每个设备独立像素(DP)的英寸数,这里设定为 1/96,这是 Android 系统的标准
  1204  	const inchPrDp = 1.0 / 96.0
  1205  	// 计算每个设备独立像素(DP)和比例独立像素(SP)的像素数
  1206  	ppdp := float32(dpi) * inchPrDp
  1207  	// 返回一个 unit.Metric 对象,其中 PxPerDp 和 PxPerSp 都设置为计算得到的像素数
  1208  	return unit.Metric{
  1209  		PxPerDp: ppdp,
  1210  		PxPerSp: ppdp,
  1211  	}
  1212  }
  1213  
  1214  // ImplementsEvent 方法是一个空实现,用于满足 Event 接口的要求
  1215  // 这里的 ViewEvent 是一个空结构体,它实现了 Event 接口,但并没有添加任何额外的方法或属性
  1216  func (_ ViewEvent) ImplementsEvent() {}