github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/winc/controlbase.go (about) 1 //go:build windows 2 3 /* 4 * Copyright (C) 2019 The Winc Authors. All Rights Reserved. 5 * Copyright (C) 2010-2013 Allen Dang. All Rights Reserved. 6 */ 7 8 package winc 9 10 import ( 11 "fmt" 12 "runtime" 13 "sync" 14 "syscall" 15 "unsafe" 16 17 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32" 18 ) 19 20 type ControlBase struct { 21 hwnd w32.HWND 22 font *Font 23 parent Controller 24 contextMenu *MenuItem 25 26 isForm bool 27 28 minWidth, minHeight int 29 maxWidth, maxHeight int 30 31 // General events 32 onCreate EventManager 33 onClose EventManager 34 35 // Focus events 36 onKillFocus EventManager 37 onSetFocus EventManager 38 39 // Drag and drop events 40 onDropFiles EventManager 41 42 // Mouse events 43 onLBDown EventManager 44 onLBUp EventManager 45 onLBDbl EventManager 46 onMBDown EventManager 47 onMBUp EventManager 48 onRBDown EventManager 49 onRBUp EventManager 50 onRBDbl EventManager 51 onMouseMove EventManager 52 53 // use MouseControl to capture onMouseHover and onMouseLeave events. 54 onMouseHover EventManager 55 onMouseLeave EventManager 56 57 // Keyboard events 58 onKeyUp EventManager 59 60 // Paint events 61 onPaint EventManager 62 onSize EventManager 63 64 m sync.Mutex 65 dispatchq []func() 66 } 67 68 // initControl is called by controls: edit, button, treeview, listview, and so on. 69 func (cba *ControlBase) InitControl(className string, parent Controller, exstyle, style uint) { 70 cba.hwnd = CreateWindow(className, parent, exstyle, style) 71 if cba.hwnd == 0 { 72 panic("cannot create window for " + className) 73 } 74 cba.parent = parent 75 } 76 77 // InitWindow is called by custom window based controls such as split, panel, etc. 78 func (cba *ControlBase) InitWindow(className string, parent Controller, exstyle, style uint) { 79 RegClassOnlyOnce(className) 80 cba.hwnd = CreateWindow(className, parent, exstyle, style) 81 if cba.hwnd == 0 { 82 panic("cannot create window for " + className) 83 } 84 cba.parent = parent 85 } 86 87 // SetTheme for TreeView and ListView controls. 88 func (cba *ControlBase) SetTheme(appName string) error { 89 if hr := w32.SetWindowTheme(cba.hwnd, syscall.StringToUTF16Ptr(appName), nil); w32.FAILED(hr) { 90 return fmt.Errorf("SetWindowTheme %d", hr) 91 } 92 return nil 93 } 94 95 func (cba *ControlBase) Handle() w32.HWND { 96 return cba.hwnd 97 } 98 99 func (cba *ControlBase) SetHandle(hwnd w32.HWND) { 100 cba.hwnd = hwnd 101 } 102 103 func (cba *ControlBase) GetWindowDPI() (w32.UINT, w32.UINT) { 104 if w32.HasGetDpiForWindowFunc() { 105 // GetDpiForWindow is supported beginning with Windows 10, 1607 and is the most accureate 106 // one, especially it is consistent with the WM_DPICHANGED event. 107 dpi := w32.GetDpiForWindow(cba.hwnd) 108 return dpi, dpi 109 } 110 111 if w32.HasGetDPIForMonitorFunc() { 112 // GetDpiForWindow is supported beginning with Windows 8.1 113 monitor := w32.MonitorFromWindow(cba.hwnd, w32.MONITOR_DEFAULTTONEAREST) 114 if monitor == 0 { 115 return 0, 0 116 } 117 var dpiX, dpiY w32.UINT 118 w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY) 119 return dpiX, dpiY 120 } 121 122 // If none of the above is supported fallback to the System DPI. 123 screen := w32.GetDC(0) 124 x := w32.GetDeviceCaps(screen, w32.LOGPIXELSX) 125 y := w32.GetDeviceCaps(screen, w32.LOGPIXELSY) 126 w32.ReleaseDC(0, screen) 127 return w32.UINT(x), w32.UINT(y) 128 } 129 130 func (cba *ControlBase) SetAndClearStyleBits(set, clear uint32) error { 131 style := uint32(w32.GetWindowLong(cba.hwnd, w32.GWL_STYLE)) 132 if style == 0 { 133 return fmt.Errorf("GetWindowLong") 134 } 135 136 if newStyle := style&^clear | set; newStyle != style { 137 if w32.SetWindowLong(cba.hwnd, w32.GWL_STYLE, newStyle) == 0 { 138 return fmt.Errorf("SetWindowLong") 139 } 140 } 141 return nil 142 } 143 144 func (cba *ControlBase) SetIsForm(isform bool) { 145 cba.isForm = isform 146 } 147 148 func (cba *ControlBase) SetText(caption string) { 149 w32.SetWindowText(cba.hwnd, caption) 150 } 151 152 func (cba *ControlBase) Text() string { 153 return w32.GetWindowText(cba.hwnd) 154 } 155 156 func (cba *ControlBase) Close() { 157 UnRegMsgHandler(cba.hwnd) 158 w32.DestroyWindow(cba.hwnd) 159 } 160 161 func (cba *ControlBase) SetTranslucentBackground() { 162 var accent = w32.ACCENT_POLICY{ 163 AccentState: w32.ACCENT_ENABLE_BLURBEHIND, 164 } 165 var data w32.WINDOWCOMPOSITIONATTRIBDATA 166 data.Attrib = w32.WCA_ACCENT_POLICY 167 data.PvData = unsafe.Pointer(&accent) 168 data.CbData = unsafe.Sizeof(accent) 169 170 w32.SetWindowCompositionAttribute(cba.hwnd, &data) 171 } 172 173 func min(a, b int) int { 174 if a < b { 175 return a 176 } 177 return b 178 } 179 180 func max(a, b int) int { 181 if a > b { 182 return a 183 } 184 return b 185 } 186 187 func (cba *ControlBase) clampSize(width, height int) (int, int) { 188 if cba.minWidth != 0 { 189 width = max(width, cba.minWidth) 190 } 191 if cba.maxWidth != 0 { 192 width = min(width, cba.maxWidth) 193 } 194 if cba.minHeight != 0 { 195 height = max(height, cba.minHeight) 196 } 197 if cba.maxHeight != 0 { 198 height = min(height, cba.maxHeight) 199 } 200 return width, height 201 } 202 203 func (cba *ControlBase) SetSize(width, height int) { 204 x, y := cba.Pos() 205 width, height = cba.clampSize(width, height) 206 width, height = cba.scaleWithWindowDPI(width, height) 207 w32.MoveWindow(cba.hwnd, x, y, width, height, true) 208 } 209 210 func (cba *ControlBase) SetMinSize(width, height int) { 211 cba.minWidth = width 212 cba.minHeight = height 213 214 // Ensure we set max if min > max 215 if cba.maxWidth > 0 { 216 cba.maxWidth = max(cba.minWidth, cba.maxWidth) 217 } 218 if cba.maxHeight > 0 { 219 cba.maxHeight = max(cba.minHeight, cba.maxHeight) 220 } 221 222 x, y := cba.Pos() 223 currentWidth, currentHeight := cba.Size() 224 clampedWidth, clampedHeight := cba.clampSize(currentWidth, currentHeight) 225 if clampedWidth != currentWidth || clampedHeight != currentHeight { 226 w32.MoveWindow(cba.hwnd, x, y, clampedWidth, clampedHeight, true) 227 } 228 } 229 func (cba *ControlBase) SetMaxSize(width, height int) { 230 cba.maxWidth = width 231 cba.maxHeight = height 232 233 // Ensure we set min if max > min 234 if cba.maxWidth > 0 { 235 cba.minWidth = min(cba.maxWidth, cba.minWidth) 236 } 237 if cba.maxHeight > 0 { 238 cba.minHeight = min(cba.maxHeight, cba.minHeight) 239 } 240 241 x, y := cba.Pos() 242 currentWidth, currentHeight := cba.Size() 243 clampedWidth, clampedHeight := cba.clampSize(currentWidth, currentHeight) 244 if clampedWidth != currentWidth || clampedHeight != currentHeight { 245 w32.MoveWindow(cba.hwnd, x, y, clampedWidth, clampedHeight, true) 246 } 247 } 248 249 func (cba *ControlBase) Size() (width, height int) { 250 rect := w32.GetWindowRect(cba.hwnd) 251 width = int(rect.Right - rect.Left) 252 height = int(rect.Bottom - rect.Top) 253 width, height = cba.scaleToDefaultDPI(width, height) 254 return 255 } 256 257 func (cba *ControlBase) Width() int { 258 rect := w32.GetWindowRect(cba.hwnd) 259 return int(rect.Right - rect.Left) 260 } 261 262 func (cba *ControlBase) Height() int { 263 rect := w32.GetWindowRect(cba.hwnd) 264 return int(rect.Bottom - rect.Top) 265 } 266 267 func (cba *ControlBase) SetPos(x, y int) { 268 info := getMonitorInfo(cba.hwnd) 269 workRect := info.RcWork 270 271 w32.SetWindowPos(cba.hwnd, w32.HWND_TOP, int(workRect.Left)+x, int(workRect.Top)+y, 0, 0, w32.SWP_NOSIZE) 272 } 273 func (cba *ControlBase) SetAlwaysOnTop(b bool) { 274 if b { 275 w32.SetWindowPos(cba.hwnd, w32.HWND_TOPMOST, 0, 0, 0, 0, w32.SWP_NOSIZE|w32.SWP_NOMOVE) 276 } else { 277 w32.SetWindowPos(cba.hwnd, w32.HWND_NOTOPMOST, 0, 0, 0, 0, w32.SWP_NOSIZE|w32.SWP_NOMOVE) 278 } 279 } 280 281 func (cba *ControlBase) Pos() (x, y int) { 282 rect := w32.GetWindowRect(cba.hwnd) 283 x = int(rect.Left) 284 y = int(rect.Top) 285 if !cba.isForm && cba.parent != nil { 286 x, y, _ = w32.ScreenToClient(cba.parent.Handle(), x, y) 287 } 288 return 289 } 290 291 func (cba *ControlBase) Visible() bool { 292 return w32.IsWindowVisible(cba.hwnd) 293 } 294 295 func (cba *ControlBase) ToggleVisible() bool { 296 visible := w32.IsWindowVisible(cba.hwnd) 297 if visible { 298 cba.Hide() 299 } else { 300 cba.Show() 301 } 302 return !visible 303 } 304 305 func (cba *ControlBase) ContextMenu() *MenuItem { 306 return cba.contextMenu 307 } 308 309 func (cba *ControlBase) SetContextMenu(menu *MenuItem) { 310 cba.contextMenu = menu 311 } 312 313 func (cba *ControlBase) Bounds() *Rect { 314 rect := w32.GetWindowRect(cba.hwnd) 315 if cba.isForm { 316 return &Rect{*rect} 317 } 318 319 return ScreenToClientRect(cba.hwnd, rect) 320 } 321 322 func (cba *ControlBase) ClientRect() *Rect { 323 rect := w32.GetClientRect(cba.hwnd) 324 return ScreenToClientRect(cba.hwnd, rect) 325 } 326 func (cba *ControlBase) ClientWidth() int { 327 rect := w32.GetClientRect(cba.hwnd) 328 return int(rect.Right - rect.Left) 329 } 330 331 func (cba *ControlBase) ClientHeight() int { 332 rect := w32.GetClientRect(cba.hwnd) 333 return int(rect.Bottom - rect.Top) 334 } 335 336 func (cba *ControlBase) Show() { 337 // WindowPos is used with HWND_TOPMOST to guarantee bring our app on top 338 // force set our main window on top 339 w32.SetWindowPos( 340 cba.hwnd, 341 w32.HWND_TOPMOST, 342 0, 0, 0, 0, 343 w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE, 344 ) 345 // remove topmost to allow normal windows manipulations 346 w32.SetWindowPos( 347 cba.hwnd, 348 w32.HWND_NOTOPMOST, 349 0, 0, 0, 0, 350 w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE, 351 ) 352 // put main window on tops foreground 353 w32.SetForegroundWindow(cba.hwnd) 354 } 355 356 func (cba *ControlBase) Hide() { 357 w32.ShowWindow(cba.hwnd, w32.SW_HIDE) 358 } 359 360 func (cba *ControlBase) Enabled() bool { 361 return w32.IsWindowEnabled(cba.hwnd) 362 } 363 364 func (cba *ControlBase) SetEnabled(b bool) { 365 w32.EnableWindow(cba.hwnd, b) 366 } 367 368 func (cba *ControlBase) SetFocus() { 369 w32.SetFocus(cba.hwnd) 370 } 371 372 func (cba *ControlBase) Invalidate(erase bool) { 373 // pRect := w32.GetClientRect(cba.hwnd) 374 // if cba.isForm { 375 // w32.InvalidateRect(cba.hwnd, pRect, erase) 376 // } else { 377 // rc := ScreenToClientRect(cba.hwnd, pRect) 378 // w32.InvalidateRect(cba.hwnd, rc.GetW32Rect(), erase) 379 // } 380 w32.InvalidateRect(cba.hwnd, nil, erase) 381 } 382 383 func (cba *ControlBase) Parent() Controller { 384 return cba.parent 385 } 386 387 func (cba *ControlBase) SetParent(parent Controller) { 388 cba.parent = parent 389 } 390 391 func (cba *ControlBase) Font() *Font { 392 return cba.font 393 } 394 395 func (cba *ControlBase) SetFont(font *Font) { 396 w32.SendMessage(cba.hwnd, w32.WM_SETFONT, uintptr(font.hfont), 1) 397 cba.font = font 398 } 399 400 func (cba *ControlBase) EnableDragAcceptFiles(b bool) { 401 w32.DragAcceptFiles(cba.hwnd, b) 402 } 403 404 func (cba *ControlBase) InvokeRequired() bool { 405 if cba.hwnd == 0 { 406 return false 407 } 408 409 windowThreadId, _ := w32.GetWindowThreadProcessId(cba.hwnd) 410 currentThreadId := w32.GetCurrentThreadId() 411 412 return windowThreadId != currentThreadId 413 } 414 415 func (cba *ControlBase) Invoke(f func()) { 416 if cba.tryInvokeOnCurrentGoRoutine(f) { 417 return 418 } 419 420 cba.m.Lock() 421 cba.dispatchq = append(cba.dispatchq, f) 422 cba.m.Unlock() 423 w32.PostMessage(cba.hwnd, wmInvokeCallback, 0, 0) 424 } 425 426 func (cba *ControlBase) PreTranslateMessage(msg *w32.MSG) bool { 427 if msg.Message == w32.WM_GETDLGCODE { 428 println("pretranslate, WM_GETDLGCODE") 429 } 430 return false 431 } 432 433 // Events 434 func (cba *ControlBase) OnCreate() *EventManager { 435 return &cba.onCreate 436 } 437 438 func (cba *ControlBase) OnClose() *EventManager { 439 return &cba.onClose 440 } 441 442 func (cba *ControlBase) OnKillFocus() *EventManager { 443 return &cba.onKillFocus 444 } 445 446 func (cba *ControlBase) OnSetFocus() *EventManager { 447 return &cba.onSetFocus 448 } 449 450 func (cba *ControlBase) OnDropFiles() *EventManager { 451 return &cba.onDropFiles 452 } 453 454 func (cba *ControlBase) OnLBDown() *EventManager { 455 return &cba.onLBDown 456 } 457 458 func (cba *ControlBase) OnLBUp() *EventManager { 459 return &cba.onLBUp 460 } 461 462 func (cba *ControlBase) OnLBDbl() *EventManager { 463 return &cba.onLBDbl 464 } 465 466 func (cba *ControlBase) OnMBDown() *EventManager { 467 return &cba.onMBDown 468 } 469 470 func (cba *ControlBase) OnMBUp() *EventManager { 471 return &cba.onMBUp 472 } 473 474 func (cba *ControlBase) OnRBDown() *EventManager { 475 return &cba.onRBDown 476 } 477 478 func (cba *ControlBase) OnRBUp() *EventManager { 479 return &cba.onRBUp 480 } 481 482 func (cba *ControlBase) OnRBDbl() *EventManager { 483 return &cba.onRBDbl 484 } 485 486 func (cba *ControlBase) OnMouseMove() *EventManager { 487 return &cba.onMouseMove 488 } 489 490 func (cba *ControlBase) OnMouseHover() *EventManager { 491 return &cba.onMouseHover 492 } 493 494 func (cba *ControlBase) OnMouseLeave() *EventManager { 495 return &cba.onMouseLeave 496 } 497 498 func (cba *ControlBase) OnPaint() *EventManager { 499 return &cba.onPaint 500 } 501 502 func (cba *ControlBase) OnSize() *EventManager { 503 return &cba.onSize 504 } 505 506 func (cba *ControlBase) OnKeyUp() *EventManager { 507 return &cba.onKeyUp 508 } 509 510 func (cba *ControlBase) scaleWithWindowDPI(width, height int) (int, int) { 511 dpix, dpiy := cba.GetWindowDPI() 512 scaledWidth := ScaleWithDPI(width, dpix) 513 scaledHeight := ScaleWithDPI(height, dpiy) 514 515 return scaledWidth, scaledHeight 516 } 517 518 func (cba *ControlBase) scaleToDefaultDPI(width, height int) (int, int) { 519 dpix, dpiy := cba.GetWindowDPI() 520 scaledWidth := ScaleToDefaultDPI(width, dpix) 521 scaledHeight := ScaleToDefaultDPI(height, dpiy) 522 523 return scaledWidth, scaledHeight 524 } 525 526 func (cba *ControlBase) tryInvokeOnCurrentGoRoutine(f func()) bool { 527 runtime.LockOSThread() 528 defer runtime.UnlockOSThread() 529 530 if cba.InvokeRequired() { 531 return false 532 } 533 f() 534 return true 535 } 536 537 func (cba *ControlBase) invokeCallbacks() { 538 runtime.LockOSThread() 539 defer runtime.UnlockOSThread() 540 541 if cba.InvokeRequired() { 542 panic("InvokeCallbacks must always be called on the window thread") 543 } 544 545 cba.m.Lock() 546 q := append([]func(){}, cba.dispatchq...) 547 cba.dispatchq = []func(){} 548 cba.m.Unlock() 549 for _, v := range q { 550 v() 551 } 552 }