mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 07:40:17 +08:00

* Set Window Background colour if provided. Added debounce option for Webview2 redraw on resize * [windows] Workaround resize flickering in frameless mode Co-authored-by: stffabi <stffabi@users.noreply.github.com>
249 lines
7.5 KiB
Go
249 lines
7.5 KiB
Go
//go:build windows
|
|
|
|
package windows
|
|
|
|
import (
|
|
"unsafe"
|
|
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
|
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
|
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
)
|
|
|
|
type Window struct {
|
|
winc.Form
|
|
frontendOptions *options.App
|
|
applicationMenu *menu.Menu
|
|
notifyParentWindowPositionChanged func() error
|
|
minWidth, minHeight, maxWidth, maxHeight int
|
|
versionInfo *operatingsystem.WindowsVersionInfo
|
|
isDarkMode bool
|
|
isActive bool
|
|
hasBeenShown bool
|
|
}
|
|
|
|
func NewWindow(parent winc.Controller, appoptions *options.App, versionInfo *operatingsystem.WindowsVersionInfo) *Window {
|
|
result := &Window{
|
|
frontendOptions: appoptions,
|
|
minHeight: appoptions.MinHeight,
|
|
minWidth: appoptions.MinWidth,
|
|
maxHeight: appoptions.MaxHeight,
|
|
maxWidth: appoptions.MaxWidth,
|
|
versionInfo: versionInfo,
|
|
isActive: true,
|
|
}
|
|
result.SetIsForm(true)
|
|
|
|
var exStyle int
|
|
if appoptions.Windows != nil {
|
|
exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW
|
|
if appoptions.Windows.WindowIsTranslucent {
|
|
exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
|
|
}
|
|
}
|
|
if appoptions.AlwaysOnTop {
|
|
exStyle |= w32.WS_EX_TOPMOST
|
|
}
|
|
|
|
var dwStyle = w32.WS_OVERLAPPEDWINDOW
|
|
|
|
winc.RegClassOnlyOnce("wailsWindow")
|
|
handle := winc.CreateWindow("wailsWindow", parent, uint(exStyle), uint(dwStyle))
|
|
result.SetHandle(handle)
|
|
winc.RegMsgHandler(result)
|
|
result.SetParent(parent)
|
|
|
|
loadIcon := true
|
|
if appoptions.Windows != nil && appoptions.Windows.DisableWindowIcon == true {
|
|
loadIcon = false
|
|
}
|
|
if loadIcon {
|
|
if ico, err := winc.NewIconFromResource(winc.GetAppInstance(), uint16(winc.AppIconID)); err == nil {
|
|
result.SetIcon(0, ico)
|
|
}
|
|
}
|
|
|
|
if appoptions.RGBA != nil {
|
|
win32.SetBackgroundColour(result.Handle(), appoptions.RGBA.R, appoptions.RGBA.G, appoptions.RGBA.B)
|
|
}
|
|
|
|
result.SetSize(appoptions.Width, appoptions.Height)
|
|
result.SetText(appoptions.Title)
|
|
result.EnableSizable(!appoptions.DisableResize)
|
|
if !appoptions.Fullscreen {
|
|
result.EnableMaxButton(!appoptions.DisableResize)
|
|
result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight)
|
|
result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
|
|
}
|
|
|
|
result.updateTheme()
|
|
|
|
if appoptions.Windows != nil {
|
|
if appoptions.Windows.WindowIsTranslucent {
|
|
// TODO: Migrate to win32 package
|
|
if !win32.IsWindowsVersionAtLeast(10, 0, 22579) {
|
|
result.SetTranslucentBackground()
|
|
} else {
|
|
win32.EnableTranslucency(result.Handle(), win32.BackdropType(appoptions.Windows.TranslucencyType))
|
|
}
|
|
}
|
|
|
|
if appoptions.Windows.DisableWindowIcon {
|
|
result.DisableIcon()
|
|
}
|
|
}
|
|
|
|
// Dlg forces display of focus rectangles, as soon as the user starts to type.
|
|
w32.SendMessage(result.Handle(), w32.WM_CHANGEUISTATE, w32.UIS_INITIALIZE, 0)
|
|
|
|
result.SetFont(winc.DefaultFont)
|
|
|
|
if appoptions.Menu != nil {
|
|
result.SetApplicationMenu(appoptions.Menu)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (w *Window) Run() int {
|
|
w.updateTheme()
|
|
return winc.RunMainLoop()
|
|
}
|
|
|
|
func (w *Window) Fullscreen() {
|
|
w.Form.SetMaxSize(0, 0)
|
|
w.Form.SetMinSize(0, 0)
|
|
w.Form.Fullscreen()
|
|
}
|
|
|
|
func (w *Window) UnFullscreen() {
|
|
if !w.IsFullScreen() {
|
|
return
|
|
}
|
|
w.Form.UnFullscreen()
|
|
w.SetMinSize(w.minWidth, w.minHeight)
|
|
w.SetMaxSize(w.maxWidth, w.maxHeight)
|
|
}
|
|
|
|
func (w *Window) SetMinSize(minWidth int, minHeight int) {
|
|
w.minWidth = minWidth
|
|
w.minHeight = minHeight
|
|
w.Form.SetMinSize(minWidth, minHeight)
|
|
}
|
|
|
|
func (w *Window) SetMaxSize(maxWidth int, maxHeight int) {
|
|
w.maxWidth = maxWidth
|
|
w.maxHeight = maxHeight
|
|
w.Form.SetMaxSize(maxWidth, maxHeight)
|
|
}
|
|
|
|
func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
|
|
|
switch msg {
|
|
case w32.WM_SETTINGCHANGE:
|
|
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lparam)))
|
|
if settingChanged == "ImmersiveColorSet" {
|
|
w.updateTheme()
|
|
}
|
|
return 0
|
|
case w32.WM_NCLBUTTONDOWN:
|
|
w32.SetFocus(w.Handle())
|
|
case w32.WM_MOVE, w32.WM_MOVING:
|
|
if w.notifyParentWindowPositionChanged != nil {
|
|
w.notifyParentWindowPositionChanged()
|
|
}
|
|
case w32.WM_ACTIVATE:
|
|
//if !w.frontendOptions.Frameless {
|
|
if int(wparam) == w32.WA_INACTIVE {
|
|
w.isActive = false
|
|
w.updateTheme()
|
|
} else {
|
|
w.isActive = true
|
|
w.updateTheme()
|
|
//}
|
|
}
|
|
|
|
// TODO move WM_DPICHANGED handling into winc
|
|
case 0x02E0: //w32.WM_DPICHANGED
|
|
newWindowSize := (*w32.RECT)(unsafe.Pointer(lparam))
|
|
w32.SetWindowPos(w.Handle(),
|
|
uintptr(0),
|
|
int(newWindowSize.Left),
|
|
int(newWindowSize.Top),
|
|
int(newWindowSize.Right-newWindowSize.Left),
|
|
int(newWindowSize.Bottom-newWindowSize.Top),
|
|
w32.SWP_NOZORDER|w32.SWP_NOACTIVATE)
|
|
}
|
|
|
|
if w.frontendOptions.Frameless {
|
|
switch msg {
|
|
case w32.WM_ACTIVATE:
|
|
// If we want to have a frameless window but with the default frame decorations, extend the DWM client area.
|
|
// This Option is not affected by returning 0 in WM_NCCALCSIZE.
|
|
// As a result we have hidden the titlebar but still have the default window frame styling.
|
|
// See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks
|
|
if winoptions := w.frontendOptions.Windows; winoptions == nil || !winoptions.DisableFramelessWindowDecorations {
|
|
win32.ExtendFrameIntoClientArea(w.Handle())
|
|
}
|
|
case w32.WM_NCCALCSIZE:
|
|
// Disable the standard frame by allowing the client area to take the full
|
|
// window size.
|
|
// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks
|
|
// This hides the titlebar and also disables the resizing from user interaction because the standard frame is not
|
|
// shown. We still need the WS_THICKFRAME style to enable resizing from the frontend.
|
|
if wparam != 0 {
|
|
rgrc := (*w32.RECT)(unsafe.Pointer(lparam))
|
|
|
|
style := uint32(w32.GetWindowLong(w.Handle(), w32.GWL_STYLE))
|
|
if style&w32.WS_MAXIMIZE != 0 {
|
|
// If the window is maximized we must adjust the client area to the work area of the monitor. Otherwise
|
|
// some content goes beyond the visible part of the monitor.
|
|
monitor := w32.MonitorFromWindow(w.Handle(), w32.MONITOR_DEFAULTTONEAREST)
|
|
|
|
var monitorInfo w32.MONITORINFO
|
|
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
|
|
if w32.GetMonitorInfo(monitor, &monitorInfo) {
|
|
*rgrc = monitorInfo.RcWork
|
|
|
|
maxWidth := w.frontendOptions.MaxWidth
|
|
maxHeight := w.frontendOptions.MaxHeight
|
|
if maxWidth > 0 || maxHeight > 0 {
|
|
var dpiX, dpiY uint
|
|
w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
|
|
|
|
maxWidth := int32(winc.ScaleWithDPI(maxWidth, dpiX))
|
|
if maxWidth > 0 && rgrc.Right-rgrc.Left > maxWidth {
|
|
rgrc.Right = rgrc.Left + maxWidth
|
|
}
|
|
|
|
maxHeight := int32(winc.ScaleWithDPI(maxHeight, dpiY))
|
|
if maxHeight > 0 && rgrc.Bottom-rgrc.Top > maxHeight {
|
|
rgrc.Bottom = rgrc.Top + maxHeight
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// This is needed to workaround the resize flickering in frameless mode with WindowDecorations
|
|
// See: https://stackoverflow.com/a/6558508
|
|
rgrc.Bottom -= 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
return w.Form.WndProc(msg, wparam, lparam)
|
|
}
|
|
|
|
func (w *Window) IsMaximised() bool {
|
|
return win32.IsWindowMaximised(w.Handle())
|
|
}
|
|
|
|
func (w *Window) IsMinimised() bool {
|
|
return win32.IsWindowMinimised(w.Handle())
|
|
}
|