Release 4.2.7+1: adaptive HiDPI window sizing (work-area DIPs + min size) and responsive CSS so the UI opens usable without stretch. Windows 4.2.7.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/jchv/go-webview2"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = (DPI_AWARENESS_CONTEXT)-4
|
||||
const dpiAwarenessContextPerMonitorAwareV2 = ^uintptr(3)
|
||||
|
||||
const (
|
||||
spiGetWorkArea = 0x0030
|
||||
monitorDefaultToNearest = 2
|
||||
)
|
||||
|
||||
type monitorInfo struct {
|
||||
CbSize uint32
|
||||
RcMonitor windows.Rect
|
||||
RcWork windows.Rect
|
||||
DwFlags uint32
|
||||
}
|
||||
|
||||
// enableProcessDPIAware opts into per-monitor DPI before any HWND is created.
|
||||
// The embedded app.manifest already declares PerMonitorV2; this is a belt-and-
|
||||
// suspenders call for builds that lose the manifest resource.
|
||||
func enableProcessDPIAware() {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("SetProcessDpiAwarenessContext"); p.Find() == nil {
|
||||
_, _, _ = p.Call(dpiAwarenessContextPerMonitorAwareV2)
|
||||
return
|
||||
}
|
||||
if p := user32.NewProc("SetProcessDPIAware"); p.Find() == nil {
|
||||
_, _, _ = p.Call()
|
||||
}
|
||||
}
|
||||
|
||||
func systemDPI() int {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("GetDpiForSystem"); p.Find() == nil {
|
||||
if r, _, _ := p.Call(); r >= 72 && r <= 480 {
|
||||
return int(r)
|
||||
}
|
||||
}
|
||||
return 96
|
||||
}
|
||||
|
||||
func windowDPI(hwnd uintptr) int {
|
||||
if hwnd != 0 {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("GetDpiForWindow"); p.Find() == nil {
|
||||
if r, _, _ := p.Call(hwnd); r >= 72 && r <= 480 {
|
||||
return int(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return systemDPI()
|
||||
}
|
||||
|
||||
func dipsToPhysical(dips, dpi int) int {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
return (dips*dpi + 48) / 96
|
||||
}
|
||||
|
||||
func physicalToDIPs(px, dpi int) int {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
return (px*96 + dpi/2) / dpi
|
||||
}
|
||||
|
||||
func primaryWorkAreaPhysical() (w, h int, ok bool) {
|
||||
var r windows.Rect
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
p := user32.NewProc("SystemParametersInfoW")
|
||||
ret, _, _ := p.Call(spiGetWorkArea, 0, uintptr(unsafe.Pointer(&r)), 0)
|
||||
if ret == 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
return int(r.Right - r.Left), int(r.Bottom - r.Top), true
|
||||
}
|
||||
|
||||
func monitorWorkAreaPhysical(hwnd uintptr) (w, h int, ok bool) {
|
||||
if hwnd == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
fromWin := user32.NewProc("MonitorFromWindow")
|
||||
getInfo := user32.NewProc("GetMonitorInfoW")
|
||||
if fromWin.Find() != nil || getInfo.Find() != nil {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
hmon, _, _ := fromWin.Call(hwnd, monitorDefaultToNearest)
|
||||
if hmon == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
var mi monitorInfo
|
||||
mi.CbSize = uint32(unsafe.Sizeof(mi))
|
||||
ret, _, _ := getInfo.Call(hmon, uintptr(unsafe.Pointer(&mi)))
|
||||
if ret == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
return int(mi.RcWork.Right - mi.RcWork.Left), int(mi.RcWork.Bottom - mi.RcWork.Top), true
|
||||
}
|
||||
|
||||
// computeClientSizeDIPs picks a comfortable client size in DIPs from the
|
||||
// monitor work area (also expressed as physical px + dpi).
|
||||
// Target: ~min(1100, workW*0.72) x min(780, workH*0.78), floored near
|
||||
// 960x680 when the screen allows, always capped with margins.
|
||||
func computeClientSizeDIPs(workWPhys, workHPhys, dpi int) (w, h int) {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
workW := physicalToDIPs(workWPhys, dpi)
|
||||
workH := physicalToDIPs(workHPhys, dpi)
|
||||
if workW < 320 {
|
||||
workW = 320
|
||||
}
|
||||
if workH < 240 {
|
||||
workH = 240
|
||||
}
|
||||
|
||||
const marginX, marginY = 64, 64
|
||||
maxW := workW - marginX
|
||||
maxH := workH - marginY
|
||||
if maxW < 480 {
|
||||
maxW = workW
|
||||
}
|
||||
if maxH < 360 {
|
||||
maxH = workH
|
||||
}
|
||||
if maxW < 480 {
|
||||
maxW = 480
|
||||
}
|
||||
if maxH < 360 {
|
||||
maxH = 360
|
||||
}
|
||||
|
||||
prefW := int(float64(workW) * 0.72)
|
||||
if prefW > 1100 {
|
||||
prefW = 1100
|
||||
}
|
||||
prefH := int(float64(workH) * 0.78)
|
||||
if prefH > 780 {
|
||||
prefH = 780
|
||||
}
|
||||
|
||||
if prefW < 960 && maxW >= 960 {
|
||||
prefW = 960
|
||||
}
|
||||
if prefH < 680 && maxH >= 680 {
|
||||
prefH = 680
|
||||
}
|
||||
|
||||
if prefW > maxW {
|
||||
prefW = maxW
|
||||
}
|
||||
if prefH > maxH {
|
||||
prefH = maxH
|
||||
}
|
||||
if prefW < 640 && maxW >= 640 {
|
||||
prefW = 640
|
||||
}
|
||||
if prefH < 480 && maxH >= 480 {
|
||||
prefH = 480
|
||||
}
|
||||
return prefW, prefH
|
||||
}
|
||||
|
||||
// initialWindowPhysicalSize estimates CreateWindow size before an HWND exists
|
||||
// (primary work area + system DPI). go-webview2 treats Width/Height as physical
|
||||
// pixels when the process is DPI-aware.
|
||||
func initialWindowPhysicalSize() (w, h int) {
|
||||
dpi := systemDPI()
|
||||
ww, wh, ok := primaryWorkAreaPhysical()
|
||||
if !ok || ww <= 0 || wh <= 0 {
|
||||
return dipsToPhysical(960, dpi), dipsToPhysical(680, dpi)
|
||||
}
|
||||
dipW, dipH := computeClientSizeDIPs(ww, wh, dpi)
|
||||
return dipsToPhysical(dipW, dpi), dipsToPhysical(dipH, dpi)
|
||||
}
|
||||
|
||||
// applyAdaptiveWindowSize sets HintMin (~720x520 DIPs) then the preferred
|
||||
// client size from the window's monitor work area, both in physical pixels.
|
||||
func applyAdaptiveWindowSize(w webview2.WebView) {
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
hwnd := uintptr(w.Window())
|
||||
dpi := windowDPI(hwnd)
|
||||
ww, wh, ok := monitorWorkAreaPhysical(hwnd)
|
||||
if !ok || ww <= 0 || wh <= 0 {
|
||||
ww, wh, _ = primaryWorkAreaPhysical()
|
||||
}
|
||||
dipW, dipH := computeClientSizeDIPs(ww, wh, dpi)
|
||||
physW := dipsToPhysical(dipW, dpi)
|
||||
physH := dipsToPhysical(dipH, dpi)
|
||||
|
||||
minW := dipsToPhysical(720, dpi)
|
||||
minH := dipsToPhysical(520, dpi)
|
||||
if minW > physW {
|
||||
minW = physW
|
||||
}
|
||||
if minH > physH {
|
||||
minH = physH
|
||||
}
|
||||
|
||||
w.SetSize(minW, minH, webview2.HintMin)
|
||||
w.SetSize(physW, physH, webview2.HintNone)
|
||||
}
|
||||
Reference in New Issue
Block a user