mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
[v3 Windows] Support setMin/MaxSize, setPosition
This commit is contained in:
parent
1f6217c0d8
commit
6e92a4f71e
6 changed files with 263 additions and 85 deletions
97
v3/STATUS.md
97
v3/STATUS.md
|
|
@ -26,55 +26,54 @@ Application interface methods
|
|||
|
||||
Webview Window Interface Methods
|
||||
|
||||
| Method | Windows | Linux | Mac | Notes |
|
||||
|----------------------------------------------------|---------|-------|-----|-------|
|
||||
| setTitle(title string) | Y | | Y | |
|
||||
| setSize(width, height int) | Y | | Y | |
|
||||
| setAlwaysOnTop(alwaysOnTop bool) | Y | | Y | |
|
||||
| setURL(url string) | | | Y | |
|
||||
| setResizable(resizable bool) | Y | | Y | |
|
||||
| setMinSize(width, height int) | | | Y | |
|
||||
| setMaxSize(width, height int) | | | Y | |
|
||||
| execJS(js string) | | | Y | |
|
||||
| restore() | | | Y | |
|
||||
| setBackgroundColour(color RGBA) | Y | | Y | |
|
||||
| run() | Y | | Y | |
|
||||
| center() | Y | | Y | |
|
||||
| size() (int, int) | | | Y | |
|
||||
| width() int | Y | | Y | |
|
||||
| height() int | Y | | Y | |
|
||||
| position() (int, int) | Y | | Y | |
|
||||
| destroy() | | | Y | |
|
||||
| reload() | | | Y | |
|
||||
| forceReload() | | | Y | |
|
||||
| toggleDevTools() | | | Y | |
|
||||
| zoomReset() | | | Y | |
|
||||
| zoomIn() | | | Y | |
|
||||
| zoomOut() | | | Y | |
|
||||
| getZoom() float64 | | | Y | |
|
||||
| setZoom(zoom float64) | | | Y | |
|
||||
| close() | | | Y | |
|
||||
| zoom() | | | Y | |
|
||||
| setHTML(html string) | | | Y | |
|
||||
| setPosition(x int, y int) | | | Y | |
|
||||
| on(eventID uint) | | | Y | |
|
||||
| minimise() | Y | | Y | |
|
||||
| unminimise() | Y | | Y | |
|
||||
| maximise() | Y | | Y | |
|
||||
| unmaximise() | Y | | Y | |
|
||||
| fullscreen() | | | Y | |
|
||||
| unfullscreen() | | | Y | |
|
||||
| isMinimised() bool | Y | | Y | |
|
||||
| isMaximised() bool | Y | | Y | |
|
||||
| isFullscreen() bool | | | Y | |
|
||||
| disableSizeConstraints() | | | Y | |
|
||||
| setFullscreenButtonEnabled(enabled bool) | | | Y | |
|
||||
| show() | Y | | Y | |
|
||||
| hide() | Y | | Y | |
|
||||
| getScreen() (*Screen, error) | | | Y | |
|
||||
| setFrameless(bool) | | | Y | |
|
||||
| openContextMenu(menu *Menu, data *ContextMenuData) | | | Y | |
|
||||
| nativeWindowHandle() (uintptr, error) | Y | | | |
|
||||
| Method | Windows | Linux | Mac | Notes |
|
||||
|----------------------------------------------------|---------|-------|-----|------------------------------------------|
|
||||
| center() | Y | | Y | |
|
||||
| close() | | | Y | |
|
||||
| destroy() | | | Y | |
|
||||
| disableSizeConstraints() | | | Y | |
|
||||
| execJS(js string) | | | Y | |
|
||||
| forceReload() | | | Y | |
|
||||
| fullscreen() | | | Y | |
|
||||
| getScreen() (*Screen, error) | | | Y | |
|
||||
| getZoom() float64 | | | Y | |
|
||||
| height() int | Y | | Y | |
|
||||
| hide() | Y | | Y | |
|
||||
| isFullscreen() bool | | | Y | |
|
||||
| isMaximised() bool | Y | | Y | |
|
||||
| isMinimised() bool | Y | | Y | |
|
||||
| maximise() | Y | | Y | |
|
||||
| minimise() | Y | | Y | |
|
||||
| nativeWindowHandle() (uintptr, error) | Y | | | |
|
||||
| on(eventID uint) | | | Y | |
|
||||
| openContextMenu(menu *Menu, data *ContextMenuData) | | | Y | |
|
||||
| position() (int, int) | Y | | Y | |
|
||||
| reload() | | | Y | |
|
||||
| run() | Y | | Y | |
|
||||
| setAlwaysOnTop(alwaysOnTop bool) | Y | | Y | |
|
||||
| setBackgroundColour(color RGBA) | Y | | Y | |
|
||||
| setFrameless(bool) | | | Y | |
|
||||
| setFullscreenButtonEnabled(enabled bool) | X | | Y | There is no fullscreen button in Windows |
|
||||
| setHTML(html string) | | | Y | |
|
||||
| setMaxSize(width, height int) | Y | | Y | |
|
||||
| setMinSize(width, height int) | Y | | Y | |
|
||||
| setPosition(x int, y int) | Y | | Y | |
|
||||
| setResizable(resizable bool) | Y | | Y | |
|
||||
| setSize(width, height int) | Y | | Y | |
|
||||
| setTitle(title string) | Y | | Y | |
|
||||
| setURL(url string) | | | Y | |
|
||||
| setZoom(zoom float64) | | | Y | |
|
||||
| show() | Y | | Y | |
|
||||
| size() (int, int) | | | Y | |
|
||||
| toggleDevTools() | | | Y | |
|
||||
| unfullscreen() | | | Y | |
|
||||
| unmaximise() | Y | | Y | |
|
||||
| unminimise() | Y | | Y | |
|
||||
| width() int | Y | | Y | |
|
||||
| zoom() | | | Y | |
|
||||
| zoomIn() | | | Y | |
|
||||
| zoomOut() | | | Y | |
|
||||
| zoomReset() | | | Y | |
|
||||
|
||||
## Runtime
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import (
|
|||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
"runtime"
|
||||
"sort"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -37,7 +34,7 @@ func (m *windowsApp) initMainLoop() {
|
|||
m.mainThreadWindowHWND = w32.CreateWindowEx(
|
||||
0,
|
||||
windowClassName,
|
||||
lo.Must(syscall.UTF16PtrFromString("__wails_hidden_mainthread")),
|
||||
w32.MustStringToUTF16Ptr("__wails_hidden_mainthread"),
|
||||
w32.WS_DISABLED,
|
||||
w32.CW_USEDEFAULT,
|
||||
w32.CW_USEDEFAULT,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func (m *MessageProcessor) processWindowMethod(method string, rw http.ResponseWr
|
|||
window.UnFullscreen()
|
||||
m.ok(rw)
|
||||
case "Minimise":
|
||||
window.Minimize()
|
||||
window.Minimise()
|
||||
m.ok(rw)
|
||||
case "UnMinimise":
|
||||
window.UnMinimise()
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var showDevTools = func(window unsafe.Pointer) {}
|
||||
|
|
@ -19,6 +16,10 @@ type windowsWebviewWindow struct {
|
|||
windowImpl unsafe.Pointer
|
||||
parent *WebviewWindow
|
||||
hwnd w32.HWND
|
||||
|
||||
// Size Restrictions
|
||||
minWidth, minHeight int
|
||||
maxWidth, maxHeight int
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) nativeWindowHandle() uintptr {
|
||||
|
|
@ -57,13 +58,13 @@ func (w *windowsWebviewWindow) setResizable(resizable bool) {
|
|||
}
|
||||
|
||||
func (w *windowsWebviewWindow) setMinSize(width, height int) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
w.minWidth = width
|
||||
w.minHeight = height
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) setMaxSize(width, height int) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
w.maxWidth = width
|
||||
w.maxHeight = height
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) execJS(js string) {
|
||||
|
|
@ -80,8 +81,15 @@ func (w *windowsWebviewWindow) run() {
|
|||
}
|
||||
|
||||
func (w *windowsWebviewWindow) _run() {
|
||||
var exStyle uint
|
||||
|
||||
// Copy options
|
||||
options := w.parent.options
|
||||
w.minWidth = options.MinWidth
|
||||
w.minHeight = options.MinHeight
|
||||
w.maxWidth = options.MaxWidth
|
||||
w.maxHeight = options.MaxHeight
|
||||
|
||||
var exStyle uint
|
||||
exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW
|
||||
if options.BackgroundType != BackgroundTypeSolid {
|
||||
exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
|
||||
|
|
@ -92,7 +100,7 @@ func (w *windowsWebviewWindow) _run() {
|
|||
w.hwnd = w32.CreateWindowEx(
|
||||
exStyle,
|
||||
windowClassName,
|
||||
lo.Must(syscall.UTF16PtrFromString(options.Title)),
|
||||
w32.MustStringToUTF16Ptr(options.Title),
|
||||
w32.WS_OVERLAPPEDWINDOW,
|
||||
w32.CW_USEDEFAULT,
|
||||
w32.CW_USEDEFAULT,
|
||||
|
|
@ -139,7 +147,6 @@ func (w *windowsWebviewWindow) _run() {
|
|||
switch options.Windows.Theme {
|
||||
case SystemDefault:
|
||||
w.updateTheme(w32.IsCurrentlyDarkMode())
|
||||
// Setup a listener to respond to theme changes
|
||||
w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func() {
|
||||
w.updateTheme(w32.IsCurrentlyDarkMode())
|
||||
})
|
||||
|
|
@ -248,8 +255,9 @@ func (w *windowsWebviewWindow) setHTML(html string) {
|
|||
}
|
||||
|
||||
func (w *windowsWebviewWindow) setPosition(x int, y int) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
info := w32.GetMonitorInfoForWindow(w.hwnd)
|
||||
workRect := info.RcWork
|
||||
w32.SetWindowPos(w.hwnd, w32.HWND_TOP, int(workRect.Left)+x, int(workRect.Top)+y, 0, 0, w32.SWP_NOSIZE)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) on(eventID uint) {
|
||||
|
|
@ -476,9 +484,75 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp
|
|||
windowsApp := globalApplication.impl.(*windowsApp)
|
||||
windowsApp.unregisterWindow(w)
|
||||
return 0
|
||||
default:
|
||||
return w32.DefWindowProc(w.hwnd, msg, wparam, lparam)
|
||||
case w32.WM_GETMINMAXINFO:
|
||||
mmi := (*w32.MINMAXINFO)(unsafe.Pointer(lparam))
|
||||
hasConstraints := false
|
||||
if w.minWidth > 0 || w.minHeight > 0 {
|
||||
hasConstraints = true
|
||||
|
||||
width, height := w.scaleWithWindowDPI(w.minWidth, w.minHeight)
|
||||
if width > 0 {
|
||||
mmi.PtMinTrackSize.X = int32(width)
|
||||
}
|
||||
if height > 0 {
|
||||
mmi.PtMinTrackSize.Y = int32(height)
|
||||
}
|
||||
}
|
||||
if w.maxWidth > 0 || w.maxHeight > 0 {
|
||||
hasConstraints = true
|
||||
|
||||
width, height := w.scaleWithWindowDPI(w.maxWidth, w.maxHeight)
|
||||
if width > 0 {
|
||||
mmi.PtMaxTrackSize.X = int32(width)
|
||||
}
|
||||
if height > 0 {
|
||||
mmi.PtMaxTrackSize.Y = int32(height)
|
||||
}
|
||||
}
|
||||
if hasConstraints {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return w32.DefWindowProc(w.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) DPI() (w32.UINT, w32.UINT) {
|
||||
if w32.HasGetDpiForWindowFunc() {
|
||||
// GetDpiForWindow is supported beginning with Windows 10, 1607 and is the most accureate
|
||||
// one, especially it is consistent with the WM_DPICHANGED event.
|
||||
dpi := w32.GetDpiForWindow(w.hwnd)
|
||||
return dpi, dpi
|
||||
}
|
||||
|
||||
if w32.HasGetDPIForMonitorFunc() {
|
||||
// GetDpiForWindow is supported beginning with Windows 8.1
|
||||
monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST)
|
||||
if monitor == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
var dpiX, dpiY w32.UINT
|
||||
w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
|
||||
return dpiX, dpiY
|
||||
}
|
||||
|
||||
// If none of the above is supported fallback to the System DPI.
|
||||
screen := w32.GetDC(0)
|
||||
x := w32.GetDeviceCaps(screen, w32.LOGPIXELSX)
|
||||
y := w32.GetDeviceCaps(screen, w32.LOGPIXELSY)
|
||||
w32.ReleaseDC(0, screen)
|
||||
return w32.UINT(x), w32.UINT(y)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) scaleWithWindowDPI(width, height int) (int, int) {
|
||||
dpix, dpiy := w.DPI()
|
||||
scaledWidth := ScaleWithDPI(width, dpix)
|
||||
scaledHeight := ScaleWithDPI(height, dpiy)
|
||||
|
||||
return scaledWidth, scaledHeight
|
||||
}
|
||||
|
||||
func ScaleWithDPI(pixels int, dpi uint) int {
|
||||
return (pixels * int(dpi)) / 96
|
||||
}
|
||||
|
||||
func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) {
|
||||
|
|
|
|||
118
v3/pkg/w32/screen.go
Normal file
118
v3/pkg/w32/screen.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
//go:build windows
|
||||
|
||||
package w32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func MonitorsEqual(first MONITORINFO, second MONITORINFO) bool {
|
||||
// Checks to make sure all the fields are the same.
|
||||
// A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API
|
||||
return first.DwFlags == second.DwFlags &&
|
||||
first.RcMonitor.Top == second.RcMonitor.Top &&
|
||||
first.RcMonitor.Bottom == second.RcMonitor.Bottom &&
|
||||
first.RcMonitor.Right == second.RcMonitor.Right &&
|
||||
first.RcMonitor.Left == second.RcMonitor.Left &&
|
||||
first.RcWork.Top == second.RcWork.Top &&
|
||||
first.RcWork.Bottom == second.RcWork.Bottom &&
|
||||
first.RcWork.Right == second.RcWork.Right &&
|
||||
first.RcWork.Left == second.RcWork.Left
|
||||
}
|
||||
|
||||
func GetMonitorInformation(hMonitor HMONITOR) (*MONITORINFO, error) {
|
||||
// Adapted from winc.utils.getMonitorInfo
|
||||
// See docs for
|
||||
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
|
||||
|
||||
var info MONITORINFO
|
||||
info.CbSize = uint32(unsafe.Sizeof(info))
|
||||
succeeded := GetMonitorInfo(hMonitor, &info)
|
||||
if !succeeded {
|
||||
return &info, fmt.Errorf("Windows call to getMonitorInfo failed")
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
type Screen struct {
|
||||
IsCurrent bool
|
||||
IsPrimary bool
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
func EnumProc(hMonitor HMONITOR, hdcMonitor HDC, lprcMonitor *RECT, screenContainer *ScreenContainer) uintptr {
|
||||
// adapted from https://stackoverflow.com/a/23492886/4188138
|
||||
|
||||
// see docs for the following pages to better understand this function
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
|
||||
|
||||
ourMonitorData := Screen{}
|
||||
currentMonHndl := MonitorFromWindow(screenContainer.mainWinHandle, MONITOR_DEFAULTTONEAREST)
|
||||
currentMonInfo, currErr := GetMonitorInformation(currentMonHndl)
|
||||
|
||||
if currErr != nil {
|
||||
screenContainer.errors = append(screenContainer.errors, currErr)
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
// not sure what the consequences of returning false are, so let's just return true and handle it ourselves
|
||||
return TRUE
|
||||
}
|
||||
|
||||
monInfo, err := GetMonitorInformation(hMonitor)
|
||||
if err != nil {
|
||||
screenContainer.errors = append(screenContainer.errors, err)
|
||||
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
||||
return TRUE
|
||||
}
|
||||
|
||||
height := lprcMonitor.Right - lprcMonitor.Left
|
||||
width := lprcMonitor.Bottom - lprcMonitor.Top
|
||||
ourMonitorData.IsPrimary = monInfo.DwFlags&MONITORINFOF_PRIMARY == 1
|
||||
ourMonitorData.Height = int(width)
|
||||
ourMonitorData.Width = int(height)
|
||||
ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo)
|
||||
|
||||
// the reason we need a container is that we have don't know how many times this function will be called
|
||||
// this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors
|
||||
// and retrieve the values after all EnumProc calls
|
||||
// If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is.
|
||||
screenContainer.monitors = append(screenContainer.monitors, ourMonitorData)
|
||||
// let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary
|
||||
screenContainer.errors = append(screenContainer.errors, nil)
|
||||
return TRUE
|
||||
}
|
||||
|
||||
type ScreenContainer struct {
|
||||
monitors []Screen
|
||||
errors []error
|
||||
mainWinHandle HWND
|
||||
}
|
||||
|
||||
func GetAllScreens(mainWinHandle HWND) ([]Screen, error) {
|
||||
// TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime
|
||||
monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle}
|
||||
returnErr := error(nil)
|
||||
var errorStrings []string
|
||||
|
||||
dc := GetDC(0)
|
||||
defer ReleaseDC(0, dc)
|
||||
succeeded := EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
|
||||
if !succeeded {
|
||||
return monitorContainer.monitors, fmt.Errorf("Windows call to EnumDisplayMonitors failed")
|
||||
}
|
||||
for idx, err := range monitorContainer.errors {
|
||||
if err != nil {
|
||||
errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorStrings) > 0 {
|
||||
returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
|
||||
}
|
||||
return monitorContainer.monitors, returnErr
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package w32
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/samber/lo"
|
||||
"log"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
|
@ -113,34 +114,23 @@ func showWindow(hwnd uintptr, cmdshow int) bool {
|
|||
}
|
||||
|
||||
func MustStringToUTF16Ptr(input string) *uint16 {
|
||||
ret, err := syscall.UTF16PtrFromString(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
return lo.Must(syscall.UTF16PtrFromString(input))
|
||||
}
|
||||
|
||||
func MustStringToUTF16uintptr(input string) uintptr {
|
||||
ret, err := syscall.UTF16PtrFromString(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ret := lo.Must(syscall.UTF16PtrFromString(input))
|
||||
return uintptr(unsafe.Pointer(ret))
|
||||
}
|
||||
|
||||
func MustUTF16FromString(input string) []uint16 {
|
||||
ret, err := syscall.UTF16FromString(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
func MustStringToUTF16(input string) []uint16 {
|
||||
return lo.Must(syscall.UTF16FromString(input))
|
||||
}
|
||||
|
||||
func CenterWindow(hwnd HWND) {
|
||||
windowInfo := getWindowInfo(hwnd)
|
||||
frameless := windowInfo.IsPopup()
|
||||
|
||||
info := getMonitorInfo(hwnd)
|
||||
info := GetMonitorInfoForWindow(hwnd)
|
||||
workRect := info.RcWork
|
||||
screenMiddleW := workRect.Left + (workRect.Right-workRect.Left)/2
|
||||
screenMiddleH := workRect.Top + (workRect.Bottom-workRect.Top)/2
|
||||
|
|
@ -164,7 +154,7 @@ func getWindowInfo(hwnd HWND) *WINDOWINFO {
|
|||
return &info
|
||||
}
|
||||
|
||||
func getMonitorInfo(hwnd HWND) *MONITORINFO {
|
||||
func GetMonitorInfoForWindow(hwnd HWND) *MONITORINFO {
|
||||
currentMonitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)
|
||||
var info MONITORINFO
|
||||
info.CbSize = uint32(unsafe.Sizeof(info))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue