From af6dda67a54565a4a175c8db16a01f0eab2cfd59 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 31 Jul 2024 15:42:21 +0200 Subject: [PATCH] app: [macOS] track window state changes initiated by the operating system Before this change, the window state was explicitly updated whenever Window.Option was called. However, the system may also change window state as a result of user gestures, but those changes did not result in ConfigEvents reflecting them. Remove the explicit state updates and track them when the system tells us it has changed. This is a step towards fixing #600 which require accurate window state tracking. References: https://todo.sr.ht/~eliasnaur/gio/600 Signed-off-by: Elias Naur --- app/os_macos.go | 221 +++++++++++++++++++++--------------------------- app/os_macos.m | 13 ++- 2 files changed, 101 insertions(+), 133 deletions(-) diff --git a/app/os_macos.go b/app/os_macos.go index f133ab10..d62bb645 100644 --- a/app/os_macos.go +++ b/app/os_macos.go @@ -109,6 +109,14 @@ static void makeKeyAndOrderFront(CFTypeRef windowRef) { } } +static void makeFirstResponder(CFTypeRef windowRef, CFTypeRef viewRef) { + @autoreleasepool { + NSWindow *window = (__bridge NSWindow *)windowRef; + NSView *view = (__bridge NSView *)viewRef; + [window makeFirstResponder:view]; + } +} + static void toggleFullScreen(CFTypeRef windowRef) { @autoreleasepool { NSWindow *window = (__bridge NSWindow *)windowRef; @@ -238,6 +246,13 @@ static int isWindowZoomed(CFTypeRef windowRef) { } } +static int isWindowMiniaturized(CFTypeRef windowRef) { + @autoreleasepool { + NSWindow *window = (__bridge NSWindow *)windowRef; + return window.miniaturized ? 1 : 0; + } +} + static void zoomWindow(CFTypeRef windowRef) { @autoreleasepool { NSWindow *window = (__bridge NSWindow *)windowRef; @@ -318,7 +333,6 @@ type window struct { view C.CFTypeRef w *callbacks anim bool - visible bool displayLink *displayLink // redraw is a single entry channel for making sure only one // display link redraw request is in flight. @@ -367,11 +381,23 @@ func (w *window) WriteClipboard(mime string, s []byte) { } func (w *window) updateWindowMode() { + w.scale = float32(C.getViewBackingScale(w.view)) + wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view)) + w.config.Size = image.Point{ + X: int(wf*w.scale + .5), + Y: int(hf*w.scale + .5), + } + w.config.Mode = Windowed + window := C.windowForView(w.view) + if window == 0 { + return + } style := int(C.getWindowStyleMask(C.windowForView(w.view))) - if style&C.NSWindowStyleMaskFullScreen != 0 { + switch { + case style&C.NSWindowStyleMaskFullScreen != 0: w.config.Mode = Fullscreen - } else { - w.config.Mode = Windowed + case C.isWindowZoomed(window) != 0: + w.config.Mode = Maximized } w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0 } @@ -379,105 +405,82 @@ func (w *window) updateWindowMode() { func (w *window) Configure(options []Option) { screenScale := float32(C.getScreenBackingScale()) cfg := configFor(screenScale) - prev := w.config - w.updateWindowMode() cnf := w.config cnf.apply(cfg, options) window := C.windowForView(w.view) + mask := C.getWindowStyleMask(window) + fullscreen := mask&C.NSWindowStyleMaskFullScreen != 0 switch cnf.Mode { case Fullscreen: - switch prev.Mode { - case Fullscreen: - case Minimized: + if C.isWindowMiniaturized(window) != 0 { C.unhideWindow(window) - fallthrough - default: - w.config.Mode = Fullscreen + } + if !fullscreen { C.toggleFullScreen(window) } case Minimized: - switch prev.Mode { - case Minimized, Fullscreen: - default: - w.config.Mode = Minimized - C.hideWindow(window) - } + C.hideWindow(window) case Maximized: - switch prev.Mode { - case Fullscreen: - case Minimized: + if C.isWindowMiniaturized(window) != 0 { C.unhideWindow(window) - fallthrough - default: - w.config.Mode = Maximized - w.setTitle(prev, cnf) - if C.isWindowZoomed(window) == 0 { - C.zoomWindow(window) - } + } + if fullscreen { + C.toggleFullScreen(window) + } + w.setTitle(cnf.Title) + if C.isWindowZoomed(window) == 0 { + C.zoomWindow(window) } case Windowed: - switch prev.Mode { - case Fullscreen: - C.toggleFullScreen(window) - case Minimized: + if C.isWindowMiniaturized(window) != 0 { C.unhideWindow(window) - case Maximized: - if C.isWindowZoomed(window) != 0 { - C.zoomWindow(window) - } } - w.config.Mode = Windowed - w.setTitle(prev, cnf) - if prev.Size != cnf.Size { - w.config.Size = cnf.Size - cnf.Size = cnf.Size.Div(int(screenScale)) - C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y)) + if fullscreen { + C.toggleFullScreen(window) } - if prev.MinSize != cnf.MinSize { - w.config.MinSize = cnf.MinSize - cnf.MinSize = cnf.MinSize.Div(int(screenScale)) - C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y)) - } - if prev.MaxSize != cnf.MaxSize { - w.config.MaxSize = cnf.MaxSize - cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) + w.setTitle(cnf.Title) + w.config.Size = cnf.Size + cnf.Size = cnf.Size.Div(int(screenScale)) + C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y)) + w.config.MinSize = cnf.MinSize + cnf.MinSize = cnf.MinSize.Div(int(screenScale)) + C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y)) + w.config.MaxSize = cnf.MaxSize + cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) + if cnf.MaxSize != (image.Point{}) { C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y)) } - } - if cnf.Decorated != prev.Decorated { - w.config.Decorated = cnf.Decorated - mask := C.getWindowStyleMask(window) - style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable) - style = C.NSWindowStyleMaskFullSizeContentView - mask &^= style - barTrans := C.int(C.NO) - titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible) - if !cnf.Decorated { - mask |= style - barTrans = C.YES - titleVis = C.NSWindowTitleHidden + if C.isWindowZoomed(window) != 0 { + C.zoomWindow(window) } - C.setWindowTitlebarAppearsTransparent(window, barTrans) - C.setWindowTitleVisibility(window, titleVis) - C.setWindowStyleMask(window, mask) - C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans) - C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans) - C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans) - // When toggling the titlebar, the layer doesn't update its frame - // until the next resize. Force it. - C.resetLayerFrame(w.view) } - w.ProcessEvent(ConfigEvent{Config: w.config}) + style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable) + style = C.NSWindowStyleMaskFullSizeContentView + mask &^= style + barTrans := C.int(C.NO) + titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible) + if !cnf.Decorated { + mask |= style + barTrans = C.YES + titleVis = C.NSWindowTitleHidden + } + C.setWindowTitlebarAppearsTransparent(window, barTrans) + C.setWindowTitleVisibility(window, titleVis) + C.setWindowStyleMask(window, mask) + C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans) + C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans) + C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans) + // When toggling the titlebar, the layer doesn't update its frame + // until the next resize. Force it. + C.resetLayerFrame(w.view) } -func (w *window) setTitle(prev, cnf Config) { - if prev.Title != cnf.Title { - w.config.Title = cnf.Title - title := stringToNSString(cnf.Title) - defer C.CFRelease(title) - C.setTitle(C.windowForView(w.view), title) - } +func (w *window) setTitle(title string) { + w.config.Title = title + titleC := stringToNSString(title) + defer C.CFRelease(titleC) + C.setTitle(C.windowForView(w.view), titleC) } func (w *window) Perform(acts system.Action) { @@ -520,7 +523,8 @@ func (w *window) SetInputHint(_ key.InputHint) {} func (w *window) SetAnimating(anim bool) { w.anim = anim - if w.anim && w.visible { + window := C.windowForView(w.view) + if w.anim && window != 0 { w.displayLink.Start() } else { w.displayLink.Stop() @@ -799,24 +803,19 @@ func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRa } func (w *window) draw() { + cnf := w.config + w.updateWindowMode() + if w.config != cnf { + w.ProcessEvent(ConfigEvent{Config: w.config}) + } select { case <-w.redraw: default: } - w.visible = true if w.anim { w.SetAnimating(w.anim) } - w.scale = float32(C.getViewBackingScale(w.view)) - wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view)) - sz := image.Point{ - X: int(wf*w.scale + .5), - Y: int(hf*w.scale + .5), - } - if sz != w.config.Size { - w.config.Size = sz - w.ProcessEvent(ConfigEvent{Config: w.config}) - } + sz := w.config.Size if sz.X == 0 || sz.Y == 0 { return } @@ -824,7 +823,7 @@ func (w *window) draw() { w.ProcessEvent(frameEvent{ FrameEvent: FrameEvent{ Now: time.Now(), - Size: w.config.Size, + Size: sz, Metric: cfg, }, Sync: true, @@ -867,7 +866,6 @@ func gio_onAttached(h C.uintptr_t, attached C.int) { w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)}) } else { w.ProcessEvent(AppKitViewEvent{}) - w.visible = false w.SetAnimating(w.anim) } } @@ -882,33 +880,6 @@ func gio_onDestroy(h C.uintptr_t) { w.view = 0 } -//export gio_onHide -func gio_onHide(h C.uintptr_t) { - w := windowFor(h) - w.visible = false - w.SetAnimating(w.anim) -} - -//export gio_onShow -func gio_onShow(h C.uintptr_t) { - w := windowFor(h) - w.draw() -} - -//export gio_onFullscreen -func gio_onFullscreen(h C.uintptr_t) { - w := windowFor(h) - w.config.Mode = Fullscreen - w.ProcessEvent(ConfigEvent{Config: w.config}) -} - -//export gio_onWindowed -func gio_onWindowed(h C.uintptr_t) { - w := windowFor(h) - w.config.Mode = Windowed - w.ProcessEvent(ConfigEvent{Config: w.config}) -} - //export gio_onFinishLaunching func gio_onFinishLaunching() { close(launched) @@ -931,10 +902,9 @@ func newWindow(win *callbacks, options []Option) { w.ProcessEvent(DestroyEvent{Err: err}) return } - window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0) + window := C.gio_createWindow(w.view, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y), 0, 0, 0, 0) // Release our reference now that the NSWindow has it. C.CFRelease(w.view) - w.updateWindowMode() w.Configure(options) if nextTopLeft.x == 0 && nextTopLeft.y == 0 { // cascadeTopLeftFromPoint treats (0, 0) as a no-op, @@ -942,6 +912,7 @@ func newWindow(win *callbacks, options []Option) { nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) } nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) + C.makeFirstResponder(window, w.view) // makeKeyAndOrderFront assumes ownership of our window reference. C.makeKeyAndOrderFront(window) }) @@ -966,9 +937,7 @@ func (w *window) init(customRenderer bool) error { return } w.runOnMain(func() { - if w.visible { - C.setNeedsDisplay(w.view) - } + C.setNeedsDisplay(w.view) }) }) w.displayLink = dl diff --git a/app/os_macos.m b/app/os_macos.m index b1940321..9c895148 100644 --- a/app/os_macos.m +++ b/app/os_macos.m @@ -23,22 +23,22 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWi - (void)windowWillMiniaturize:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; GioView *view = (GioView *)window.contentView; - gio_onHide(view.handle); + gio_onDraw(view.handle); } - (void)windowDidDeminiaturize:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; GioView *view = (GioView *)window.contentView; - gio_onShow(view.handle); + gio_onDraw(view.handle); } - (void)windowWillEnterFullScreen:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; GioView *view = (GioView *)window.contentView; - gio_onFullscreen(view.handle); + gio_onDraw(view.handle); } - (void)windowWillExitFullScreen:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; GioView *view = (GioView *)window.contentView; - gio_onWindowed(view.handle); + gio_onDraw(view.handle); } - (void)windowDidChangeScreen:(NSNotification *)notification { NSWindow *window = (NSWindow *)[notification object]; @@ -202,10 +202,10 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl return [[self window] convertRectToScreen:r]; } - (void)applicationWillUnhide:(NSNotification *)notification { - gio_onShow(self.handle); + gio_onDraw(self.handle); } - (void)applicationDidHide:(NSNotification *)notification { - gio_onHide(self.handle); + gio_onDraw(self.handle); } - (void)dealloc { gio_onDestroy(self.handle); @@ -387,7 +387,6 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF [window setAcceptsMouseMovedEvents:YES]; NSView *view = (__bridge NSView *)viewRef; [window setContentView:view]; - [window makeFirstResponder:view]; window.delegate = globalWindowDel; return (__bridge_retained CFTypeRef)window; }