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  }