From 9cfbdafe145bd9f37f173fa58c40c26deafe50d1 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 22 Jun 2020 12:49:41 +0200 Subject: [PATCH] app/window,app/internal/window: allow min/max window size The app.MinSize and app.MaxSize options restricts the window size: w := app.NewWindow( app.Size(unit.Dp(600), unit.Dp(596)), app.MinSize(unit.Dp(600), unit.Dp(596)), app.MaxSize(unit.Dp(600), unit.Dp(596)), app.Title(APPNAME), ) Signed-off-by: Jason --- app/internal/window/os_macos.go | 13 ++++++- app/internal/window/os_macos.m | 8 +++- app/internal/window/os_windows.go | 50 +++++++++++++++++++++++- app/internal/window/os_x11.go | 15 ++++++++ app/internal/window/window.go | 6 ++- app/internal/windows/windows.go | 63 ++++++++++++++++++------------- app/window.go | 28 ++++++++++++++ 7 files changed, 150 insertions(+), 33 deletions(-) diff --git a/app/internal/window/os_macos.go b/app/internal/window/os_macos.go index c676bdd3..43e32405 100644 --- a/app/internal/window/os_macos.go +++ b/app/internal/window/os_macos.go @@ -41,7 +41,7 @@ __attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void); __attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length); __attribute__ ((visibility ("hidden"))) void gio_setNeedsDisplay(CFTypeRef viewRef); __attribute__ ((visibility ("hidden"))) void gio_appTerminate(void); -__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height); +__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight); __attribute__ ((visibility ("hidden"))) void gio_makeKeyAndOrderFront(CFTypeRef windowRef); __attribute__ ((visibility ("hidden"))) NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft); __attribute__ ((visibility ("hidden"))) void gio_close(CFTypeRef windowRef); @@ -323,12 +323,21 @@ func NewWindow(win Callbacks, opts *Options) error { // Window sizes is in unscaled screen coordinates, not device pixels. width = int(float32(width) / screenScale) height = int(float32(height) / screenScale) + minWidth := cfg.Px(opts.MinWidth) + minHeight := cfg.Px(opts.MinHeight) + minWidth = int(float32(minWidth) / screenScale) + minHeight = int(float32(minHeight) / screenScale) + maxWidth := cfg.Px(opts.MaxWidth) + maxHeight := cfg.Px(opts.MaxHeight) + maxWidth = int(float32(maxWidth) / screenScale) + maxHeight = int(float32(maxHeight) / screenScale) title := C.CString(opts.Title) defer C.free(unsafe.Pointer(title)) errch <- nil win.SetDriver(w) w.w = win - w.window = C.gio_createWindow(w.view, title, C.CGFloat(width), C.CGFloat(height)) + w.window = C.gio_createWindow(w.view, title, C.CGFloat(width), C.CGFloat(height), + C.CGFloat(minWidth), C.CGFloat(minHeight), C.CGFloat(maxWidth), C.CGFloat(maxHeight)) if nextTopLeft.x == 0 && nextTopLeft.y == 0 { // cascadeTopLeftFromPoint treats (0, 0) as a no-op, // and just returns the offset we need for the first window. diff --git a/app/internal/window/os_macos.m b/app/internal/window/os_macos.m index bd7ce288..44610813 100644 --- a/app/internal/window/os_macos.m +++ b/app/internal/window/os_macos.m @@ -124,7 +124,7 @@ void gio_makeKeyAndOrderFront(CFTypeRef windowRef) { [window makeKeyAndOrderFront:nil]; } -CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height) { +CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) { @autoreleasepool { NSRect rect = NSMakeRect(0, 0, width, height); NSUInteger styleMask = NSTitledWindowMask | @@ -136,6 +136,12 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; + if (minWidth > 0 || minHeight > 0) { + window.contentMinSize = NSMakeSize(minWidth, minHeight); + } + if (maxWidth > 0 || maxHeight > 0) { + window.contentMaxSize = NSMakeSize(maxWidth, maxHeight); + } [window setAcceptsMouseMovedEvents:YES]; window.title = [NSString stringWithUTF8String: title]; NSView *view = (__bridge NSView *)viewRef; diff --git a/app/internal/window/os_windows.go b/app/internal/window/os_windows.go index e88da34e..aed71fff 100644 --- a/app/internal/window/os_windows.go +++ b/app/internal/window/os_windows.go @@ -27,6 +27,16 @@ import ( "gioui.org/io/system" ) +type winConstraints struct { + minWidth, minHeight int32 + maxWidth, maxHeight int32 +} + +type winDeltas struct { + width int32 + height int32 +} + type window struct { hwnd syscall.Handle hdc syscall.Handle @@ -39,6 +49,10 @@ type window struct { mu sync.Mutex animating bool + + minmax winConstraints + deltas winDeltas + opts *Options } const _WM_REDRAW = windows.WM_USER + 0 @@ -137,6 +151,15 @@ func initResources() error { return nil } +func getWindowConstraints(cfg unit.Metric, opts *Options, d winDeltas) winConstraints { + var minmax winConstraints + minmax.minWidth = int32(cfg.Px(opts.MinWidth)) + d.width + minmax.minHeight = int32(cfg.Px(opts.MinHeight)) + d.height + minmax.maxWidth = int32(cfg.Px(opts.MaxWidth)) + d.width + minmax.maxHeight = int32(cfg.Px(opts.MaxHeight)) + d.height + return minmax +} + func createNativeWindow(opts *Options) (*window, error) { var resErr error resources.once.Do(func() { @@ -152,7 +175,14 @@ func createNativeWindow(opts *Options) (*window, error) { } dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW) dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE) + deltas := winDeltas{ + width: wr.Right, + height: wr.Bottom, + } windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle) + deltas.width = wr.Right - wr.Left - deltas.width + deltas.height = wr.Bottom - wr.Top - deltas.height + hwnd, err := windows.CreateWindowEx(dwExStyle, resources.class, opts.Title, @@ -168,7 +198,10 @@ func createNativeWindow(opts *Options) (*window, error) { return nil, err } w := &window{ - hwnd: hwnd, + hwnd: hwnd, + minmax: getWindowConstraints(cfg, opts, deltas), + deltas: deltas, + opts: opts, } w.hdc, err = windows.GetDC(hwnd) if err != nil { @@ -251,6 +284,20 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED: w.setStage(system.StageRunning) } + case windows.WM_GETMINMAXINFO: + mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam))) + if w.minmax.minWidth > 0 || w.minmax.minHeight > 0 { + mm.PtMinTrackSize = windows.Point{ + w.minmax.minWidth, + w.minmax.minHeight, + } + } + if w.minmax.maxWidth > 0 || w.minmax.maxHeight > 0 { + mm.PtMaxTrackSize = windows.Point{ + w.minmax.maxWidth, + w.minmax.maxHeight, + } + } } return windows.DefWindowProc(hwnd, msg, wParam, lParam) @@ -375,6 +422,7 @@ func (w *window) draw(sync bool) { return } cfg := configForDC() + w.minmax = getWindowConstraints(cfg, w.opts, w.deltas) w.w.Event(FrameEvent{ FrameEvent: system.FrameEvent{ Now: time.Now(), diff --git a/app/internal/window/os_x11.go b/app/internal/window/os_x11.go index 4f6706af..a0ece140 100644 --- a/app/internal/window/os_x11.go +++ b/app/internal/window/os_x11.go @@ -514,6 +514,21 @@ func newX11Window(gioWin Callbacks, opts *Options) error { hints.flags = C.InputHint C.XSetWMHints(dpy, win, &hints) + var shints C.XSizeHints + if opts.MinWidth.V != 0 || opts.MinHeight.V != 0 { + shints.min_width = C.int(cfg.Px(opts.MinWidth)) + shints.min_height = C.int(cfg.Px(opts.MinHeight)) + shints.flags = C.PMinSize + } + if opts.MaxWidth.V != 0 || opts.MaxHeight.V != 0 { + shints.max_width = C.int(cfg.Px(opts.MaxWidth)) + shints.max_height = C.int(cfg.Px(opts.MaxHeight)) + shints.flags = shints.flags | C.PMaxSize + } + if shints.flags != 0 { + C.XSetWMNormalHints(dpy, win, &shints) + } + name := C.CString(filepath.Base(os.Args[0])) defer C.free(unsafe.Pointer(name)) wmhints := C.XClassHint{name, name} diff --git a/app/internal/window/window.go b/app/internal/window/window.go index 1191d1e2..30ba3603 100644 --- a/app/internal/window/window.go +++ b/app/internal/window/window.go @@ -14,8 +14,10 @@ import ( ) type Options struct { - Width, Height unit.Value - Title string + Width, Height unit.Value + MinWidth, MinHeight unit.Value + MaxWidth, MaxHeight unit.Value + Title string } type FrameEvent struct { diff --git a/app/internal/windows/windows.go b/app/internal/windows/windows.go index c14442c6..90063261 100644 --- a/app/internal/windows/windows.go +++ b/app/internal/windows/windows.go @@ -46,6 +46,14 @@ type Point struct { X, Y int32 } +type MinMaxInfo struct { + PtReserved Point + PtMaxSize Point + PtMaxPosition Point + PtMinTrackSize Point + PtMaxTrackSize Point +} + const ( CS_HREDRAW = 0x0002 CS_VREDRAW = 0x0001 @@ -120,33 +128,34 @@ const ( UNICODE_NOCHAR = 65535 - WM_CANCELMODE = 0x001F - WM_CHAR = 0x0102 - WM_CREATE = 0x0001 - WM_DPICHANGED = 0x02E0 - WM_DESTROY = 0x0002 - WM_ERASEBKGND = 0x0014 - WM_KEYDOWN = 0x0100 - WM_KEYUP = 0x0101 - WM_LBUTTONDOWN = 0x0201 - WM_LBUTTONUP = 0x0202 - WM_MBUTTONDOWN = 0x0207 - WM_MBUTTONUP = 0x0208 - WM_MOUSEMOVE = 0x0200 - WM_MOUSEWHEEL = 0x020A - WM_PAINT = 0x000F - WM_CLOSE = 0x0010 - WM_QUIT = 0x0012 - WM_SETFOCUS = 0x0007 - WM_KILLFOCUS = 0x0008 - WM_SHOWWINDOW = 0x0018 - WM_SIZE = 0x0005 - WM_SYSKEYDOWN = 0x0104 - WM_RBUTTONDOWN = 0x0204 - WM_RBUTTONUP = 0x0205 - WM_TIMER = 0x0113 - WM_UNICHAR = 0x0109 - WM_USER = 0x0400 + WM_CANCELMODE = 0x001F + WM_CHAR = 0x0102 + WM_CREATE = 0x0001 + WM_DPICHANGED = 0x02E0 + WM_DESTROY = 0x0002 + WM_ERASEBKGND = 0x0014 + WM_KEYDOWN = 0x0100 + WM_KEYUP = 0x0101 + WM_LBUTTONDOWN = 0x0201 + WM_LBUTTONUP = 0x0202 + WM_MBUTTONDOWN = 0x0207 + WM_MBUTTONUP = 0x0208 + WM_MOUSEMOVE = 0x0200 + WM_MOUSEWHEEL = 0x020A + WM_PAINT = 0x000F + WM_CLOSE = 0x0010 + WM_QUIT = 0x0012 + WM_SETFOCUS = 0x0007 + WM_KILLFOCUS = 0x0008 + WM_SHOWWINDOW = 0x0018 + WM_SIZE = 0x0005 + WM_SYSKEYDOWN = 0x0104 + WM_RBUTTONDOWN = 0x0204 + WM_RBUTTONUP = 0x0205 + WM_TIMER = 0x0113 + WM_UNICHAR = 0x0109 + WM_USER = 0x0400 + WM_GETMINMAXINFO = 0x0024 WS_CLIPCHILDREN = 0x00010000 WS_CLIPSIBLINGS = 0x04000000 diff --git a/app/window.go b/app/window.go index 0a7d9d31..6b665112 100644 --- a/app/window.go +++ b/app/window.go @@ -427,4 +427,32 @@ func Size(w, h unit.Value) Option { } } +// MaxSize sets the maximum size of the window. +func MaxSize(w, h unit.Value) Option { + if w.V <= 0 { + panic("width must be larger than or equal to 0") + } + if h.V <= 0 { + panic("height must be larger than or equal to 0") + } + return func(opts *window.Options) { + opts.MaxWidth = w + opts.MaxHeight = h + } +} + +// MinSize sets the minimum size of the window. +func MinSize(w, h unit.Value) Option { + if w.V <= 0 { + panic("width must be larger than or equal to 0") + } + if h.V <= 0 { + panic("height must be larger than or equal to 0") + } + return func(opts *window.Options) { + opts.MinWidth = w + opts.MinHeight = h + } +} + func (driverEvent) ImplementsEvent() {}