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() {}