~eliasnaur/gio-patches

app/window,app/internal/window: allow min/max window size v5 PROPOSED

Jason: 1
 app/window,app/internal/window: allow min/max window size

 7 files changed, 159 insertions(+), 33 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/11288/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH v5] app/window,app/internal/window: allow min/max window size Export this patch

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 <sourcehut@sweatyballs.es>
---
 app/internal/window/os_macos.go   | 13 ++++++-
 app/internal/window/os_macos.m    |  8 +++-
 app/internal/window/os_windows.go | 59 ++++++++++++++++++++++++++++-
 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, 159 insertions(+), 33 deletions(-)

diff --git a/app/internal/window/os_macos.go b/app/internal/window/os_macos.go
index c676bdd..43e3240 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 bd7ce28..4461081 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 e88da34..3b8b45e 100644
--- a/app/internal/window/os_windows.go
+++ b/app/internal/window/os_windows.go
@@ -27,6 +27,11 @@ import (
	"gioui.org/io/system"
)

type winConstraints struct {
	minWidth, minHeight int32
	maxWidth, maxHeight int32
}

type window struct {
	hwnd        syscall.Handle
	hdc         syscall.Handle
@@ -39,6 +44,9 @@ type window struct {

	mu        sync.Mutex
	animating bool

	minmax winConstraints
	opts   *Options
}

const _WM_REDRAW = windows.WM_USER + 0
@@ -137,6 +145,28 @@ func initResources() error {
	return nil
}

func getWindowConstraints(opts *Options, cfg *unit.Metric) winConstraints {
	var minmax winConstraints
	minmax.minWidth = int32(cfg.Px(opts.MinWidth))
	minmax.minHeight = int32(cfg.Px(opts.MinHeight))
	minmax.maxWidth = int32(cfg.Px(opts.MaxWidth))
	minmax.maxHeight = int32(cfg.Px(opts.MaxHeight))
	return minmax
}

func convertDelta(opt *unit.Value, cfg *unit.Metric, delta int32) {
	switch opt.U {
	case unit.UnitDp:
		if cfg.PxPerDp != 0 {
			opt.V += float32(delta) / cfg.PxPerDp
		}
	case unit.UnitSp:
		if cfg.PxPerDp != 0 {
			opt.V += float32(delta) / cfg.PxPerSp
		}
	}
}

func createNativeWindow(opts *Options) (*window, error) {
	var resErr error
	resources.once.Do(func() {
@@ -152,7 +182,17 @@ func createNativeWindow(opts *Options) (*window, error) {
	}
	dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
	dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
	deltaWidth := wr.Right
	deltaHeight := wr.Bottom
	windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)

	deltaWidth = wr.Right - wr.Left - deltaWidth
	deltaHeight = wr.Bottom - wr.Top - deltaHeight
	convertDelta(&opts.MinWidth, &cfg, deltaWidth)
	convertDelta(&opts.MinHeight, &cfg, deltaHeight)
	convertDelta(&opts.MaxWidth, &cfg, deltaWidth)
	convertDelta(&opts.MaxHeight, &cfg, deltaHeight)

	hwnd, err := windows.CreateWindowEx(dwExStyle,
		resources.class,
		opts.Title,
@@ -168,7 +208,9 @@ func createNativeWindow(opts *Options) (*window, error) {
		return nil, err
	}
	w := &window{
		hwnd: hwnd,
		hwnd:   hwnd,
		minmax: getWindowConstraints(opts, &cfg),
		opts:   opts,
	}
	w.hdc, err = windows.GetDC(hwnd)
	if err != nil {
@@ -251,6 +293,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 +431,7 @@ func (w *window) draw(sync bool) {
		return
	}
	cfg := configForDC()
	w.minmax = getWindowConstraints(w.opts, &cfg)
	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 4f6706a..a0ece14 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 1191d1e..30ba360 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 c14442c..9006326 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 0a7d9d3..6b66511 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() {}
-- 
2.27.0