5 Commits

Author SHA1 Message Date
Elias Naur 3879921b80 app: [API] remove Main
All platforms already allow the omission of the call to Main and running
Windows on the main goroutine. This change just gets rid of Main, and
documents the special requirement on Window.Event.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:53:33 +00:00
Elias Naur b1f84da679 app: [macOS] implement custom event dispatching
To get rid of app.Main, we need to control the main thread. The macOS
[NSApp run] must be called on the main goroutine and never yields control.

Implement the escape hatch which is calling [NSApp stop] to force
[NSApp run] to return and allow us to fetch and dispatch events one at a
time.

This change is separate from the larger change removing app.Main to ease
bisecting.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:53:12 +00:00
Elias Naur 43024fcca2 app: [macOS] implement non-blocking window resizing
Despite the option to control the main thread event loop, some gestures
still block the event loop until completion. This change disables the
blocking resize gestures and implements a non-blocking replacement.

A complete replacement is left for future work, or the implementation of
https://github.com/golang/go/issues/64755.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur 9fe8b684e2 app: introduce Config.Focused that tracks the window focus state
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur 2a18a0c135 app: [macOS] synchronize rendering with Core Animation for smooth resizes
Magic incantations lifted from

https://thume.ca/2019/06/19/glitchless-metal-window-resizing/

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
20 changed files with 394 additions and 124 deletions
-12
View File
@@ -120,18 +120,6 @@ func DataDir() (string, error) {
return dataDir()
}
// Main must be called last from the program main function.
// On most platforms Main blocks forever, for Android and
// iOS it returns immediately to give control of the main
// thread back to the system.
//
// Calling Main is necessary because some operating systems
// require control of the main thread of the program for
// running windows.
func Main() {
osMain()
}
func (FrameEvent) ImplementsEvent() {}
func init() {
+5 -21
View File
@@ -33,28 +33,12 @@ For example:
A program must keep receiving events from the event channel until
[DestroyEvent] is received.
# Main
# Main Thread
The Main function must be called from a program's main function, to hand over
control of the main thread to operating systems that need it.
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
For example, to display a blank but otherwise functional window:
package main
import "gioui.org/app"
func main() {
go func() {
w := app.NewWindow()
for {
w.Event()
}
}()
app.Main()
}
Some GUI platform need access to the main thread of the program. To avoid a
deadlock on such platforms, at least one Window must have its Event method
called by the main goroutine. It doesn't have to be any particular Window;
even a destroyed Window suffices.
# Permissions
+2 -1
View File
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
[cmdBuffer presentDrawable:drawable];
[cmdBuffer commit];
[cmdBuffer waitUntilScheduled];
[drawable present];
}
}
+4 -1
View File
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
@autoreleasepool {
UIView *view = (__bridge UIView *)viewRef;
return CFBridgingRetain(view.layer);
CAMetalLayer *l = (CAMetalLayer *)view.layer;
l.needsDisplayOnBoundsChange = YES;
l.presentsWithTransaction = YES;
return CFBridgingRetain(l);
}
}
+5 -1
View File
@@ -14,7 +14,11 @@ package app
CALayer *gio_layerFactory(void) {
@autoreleasepool {
return [CAMetalLayer layer];
CAMetalLayer *l = [CAMetalLayer layer];
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
l.needsDisplayOnBoundsChange = YES;
l.presentsWithTransaction = YES;
return l;
}
}
+2
View File
@@ -45,6 +45,8 @@ type Config struct {
CustomRenderer bool
// Decorated reports whether window decorations are provided automatically.
Decorated bool
// Focused reports whether has the keyboard focus.
Focused bool
// decoHeight is the height of the fallback decoration for platforms such
// as Wayland that may need fallback client-side decorations.
decoHeight unit.Dp
+2 -4
View File
@@ -593,7 +593,8 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
//export Java_org_gioui_GioView_onFocusChange
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
w := cgo.Handle(view).Value().(*window)
w.processEvent(key.FocusEvent{Focus: focus == C.JNI_TRUE})
w.config.Focused = focus == C.JNI_TRUE
w.processEvent(ConfigEvent{Config: w.config})
}
//export Java_org_gioui_GioView_onWindowInsets
@@ -1316,9 +1317,6 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
return C.jni_FindClass(env, cn)
}
func osMain() {
}
func newWindow(window *callbacks, options []Option) {
mainWindow.in <- windowAndConfig{window, options}
<-mainWindow.windows
+7 -10
View File
@@ -15,8 +15,8 @@ __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
static bool isMainThread() {
return [NSThread isMainThread];
static int isMainThread() {
return [NSThread isMainThread] ? 1 : 0;
}
static NSUInteger nsstringLength(CFTypeRef cstr) {
@@ -77,7 +77,7 @@ var mainFuncs = make(chan func(), 1)
// runOnMain runs the function on the main thread.
func runOnMain(f func()) {
if C.isMainThread() {
if isMainThread() {
f()
return
}
@@ -87,6 +87,10 @@ func runOnMain(f func()) {
}()
}
func isMainThread() bool {
return C.isMainThread() != 0
}
//export gio_dispatchMainFuncs
func gio_dispatchMainFuncs() {
for {
@@ -259,10 +263,3 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
return to
}
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
w.loop.FlushEvents()
})
}
-11
View File
@@ -1,11 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
#import <Foundation/Foundation.h>
#include "_cgo_export.h"
void gio_wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_dispatchMainFuncs();
});
}
+9 -4
View File
@@ -211,7 +211,8 @@ func onDestroy(h C.uintptr_t) {
//export onFocus
func onFocus(h C.uintptr_t, focus int) {
w := viewFor(h)
w.ProcessEvent(key.FocusEvent{Focus: focus != 0})
w.config.Focused = focus != 0
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export onLowMemory
@@ -387,13 +388,17 @@ func newWindow(win *callbacks, options []Option) {
<-mainWindow.windows
}
func osMain() {
}
//export gio_runMain
func gio_runMain() {
runMain()
}
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
w.loop.FlushEvents()
})
}
func (UIKitViewEvent) implementsViewEvent() {}
func (UIKitViewEvent) ImplementsEvent() {}
+6
View File
@@ -276,3 +276,9 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
GioView *v = (__bridge GioView *)viewRef;
v.handle = handle;
}
void gio_wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_dispatchMainFuncs();
});
}
+4 -6
View File
@@ -258,11 +258,13 @@ func (w *window) addEventListeners() {
return nil
})
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
w.processEvent(key.FocusEvent{Focus: true})
w.config.Focused = true
w.processEvent(ConfigEvent{Config: w.config})
return nil
})
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
w.processEvent(key.FocusEvent{Focus: false})
w.config.Focused = false
w.processEvent(ConfigEvent{Config: w.config})
w.blur()
return nil
})
@@ -739,10 +741,6 @@ func (w *window) navigationColor(c color.NRGBA) {
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
}
func osMain() {
select {}
}
func translateKey(k string) (key.Name, bool) {
var n key.Name
+76 -22
View File
@@ -39,7 +39,7 @@ import (
#define MOUSE_DOWN 3
#define MOUSE_SCROLL 4
__attribute__ ((visibility ("hidden"))) void gio_main(void);
__attribute__ ((visibility ("hidden"))) void gio_initApp(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
@@ -289,6 +289,16 @@ static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
}
}
}
static void dispatchEvent(void) {
@autoreleasepool {
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
}
}
*/
import "C"
@@ -323,13 +333,16 @@ type window struct {
config Config
}
// launched is closed when applicationDidFinishLaunching is called.
// launched is closed after gio_initApp returns.
var launched = make(chan struct{})
// nextTopLeft is the offset to use for the next window's call to
// cascadeTopLeftFromPoint.
var nextTopLeft C.NSPoint
// mainThreadWindow is the window currently in control of the main thread.
var mainThreadWindow *window
func windowFor(h C.uintptr_t) *window {
return cgo.Handle(h).Value().(*window)
}
@@ -612,8 +625,9 @@ func gio_onDraw(h C.uintptr_t) {
//export gio_onFocus
func gio_onFocus(h C.uintptr_t, focus C.int) {
w := windowFor(h)
w.ProcessEvent(key.FocusEvent{Focus: focus == 1})
w.SetCursor(w.cursor)
w.config.Focused = focus == 1
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export gio_onChangeScreen
@@ -821,23 +835,51 @@ func (w *window) draw() {
func (w *window) ProcessEvent(e event.Event) {
w.w.ProcessEvent(e)
w.loop.FlushEvents()
// The main thread window deliver events in Event.
if w != mainThreadWindow {
w.loop.FlushEvents()
}
}
func (w *window) Event() event.Event {
return w.loop.Event()
if !isMainThread() {
return w.loop.Event()
}
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
for {
if evt, ok := w.loop.win.nextEvent(); ok {
return evt
}
C.dispatchEvent()
gio_dispatchMainFuncs()
}
}
func (w *window) Invalidate() {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Invalidate()
}
func (w *window) Run(f func()) {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Run(f)
}
func (w *window) Frame(frame *op.Ops) {
w.loop.Frame(frame)
if !isMainThread() {
w.loop.Frame(frame)
return
}
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
w.loop.win.ProcessFrame(frame, nil)
}
func configFor(scale float32) unit.Metric {
@@ -897,20 +939,27 @@ func gio_onWindowed(h C.uintptr_t) {
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export gio_onFinishLaunching
func gio_onFinishLaunching() {
close(launched)
}
func newWindow(win *callbacks, options []Option) {
<-launched
res := make(chan struct{})
runOnMain(func() {
w := &window{
redraw: make(chan struct{}, 1),
w: win,
w := &window{
redraw: make(chan struct{}, 1),
w: win,
}
w.loop = newEventLoop(w.w, w.wakeup)
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
select {
case <-launched:
default:
// If we're the main thread, initialize the GUI.
C.gio_initApp()
close(launched)
}
w.loop = newEventLoop(w.w, w.wakeup)
} else {
<-launched
}
res := make(chan struct{}, 1)
runOnMain(func() {
win.SetDriver(w)
res <- struct{}{}
if err := w.init(); err != nil {
@@ -963,10 +1012,6 @@ func (w *window) init() error {
return nil
}
func osMain() {
C.gio_main()
}
func convertKey(k rune) (key.Name, bool) {
var n key.Name
switch k {
@@ -1051,5 +1096,14 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
return kmods
}
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
if w != mainThreadWindow {
w.loop.FlushEvents()
}
})
}
func (AppKitViewEvent) implementsViewEvent() {}
func (AppKitViewEvent) ImplementsEvent() {}
+241 -12
View File
@@ -69,6 +69,212 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
}
@interface GioApplication: NSApplication
@end
// Variables for tracking resizes.
static struct {
NSPoint dir;
NSEvent *lastMouseDown;
NSPoint off;
} resizeState = {};
static NSBitmapImageRep *nsImageBitmap(NSImage *img) {
NSArray<NSImageRep *> *reps = img.representations;
if ([reps count] == 0) {
return nil;
}
NSImageRep *rep = reps[0];
if (![rep isKindOfClass:[NSBitmapImageRep class]]) {
return nil;
}
return (NSBitmapImageRep *)rep;
}
static NSCursor *lookupPrivateNSCursor(SEL name) {
if (![NSCursor respondsToSelector:name]) {
return nil;
}
id obj = [NSCursor performSelector:name];
if (![obj isKindOfClass:[NSCursor class]]) {
return nil;
}
return (NSCursor *)obj;
}
static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
NSCursor *c2 = lookupPrivateNSCursor(name2);
if (c2 == nil || !NSEqualPoints(c1.hotSpot, c2.hotSpot)) {
return NO;
}
NSImage *img1 = c1.image;
NSImage *img2 = c2.image;
if (!NSEqualSizes(img1.size, img2.size)) {
return NO;
}
NSBitmapImageRep *bit1 = nsImageBitmap(img1);
NSBitmapImageRep *bit2 = nsImageBitmap(img2);
if (bit1 == nil || bit2 == nil) {
return NO;
}
NSInteger n1 = bit1.numberOfPlanes*bit1.bytesPerPlane;
NSInteger n2 = bit1.numberOfPlanes*bit1.bytesPerPlane;
if (n1 != n2) {
return NO;
}
if (memcmp(bit1.bitmapData, bit2.bitmapData, n1) != 0) {
return NO;
}
return YES;
}
@implementation GioApplication
- (NSEvent *)nextEventMatchingMask:(NSEventMask)mask
untilDate:(NSDate *)expiration
inMode:(NSRunLoopMode)mode
dequeue:(BOOL)deqFlag {
if ([mode isEqualToString:NSEventTrackingRunLoopMode]) {
NSEvent *l = resizeState.lastMouseDown;
if (l != nil) {
//lastMouseDown = nil;
NSCursor *cur = [NSCursor currentSystemCursor];
NSPoint dir = {};
NSPoint off = {};
NSSize wsz = [l window].frame.size;
NSPoint center = NSMakePoint(wsz.width/2, wsz.height/2);
NSPoint p = [l locationInWindow];
if (p.x >= center.x) {
dir.x = 1;
off.x = p.x - wsz.width;
} else {
dir.x = -1;
off.x = p.x;
}
if (p.y >= center.y) {
dir.y = 1;
off.y = p.y - wsz.height;
} else {
dir.y = -1;
off.y = p.y;
}
// The button down coordinate distinguish the four quadrants. Use the
// cursor image to determine the precise direction.
SEL nw = @selector(_windowResizeNorthWestCursor);
SEL n = @selector(_windowResizeNorthCursor);
SEL ne = @selector(_windowResizeNorthEastCursor);
SEL e = @selector(_windowResizeEastCursor);
SEL se = @selector(_windowResizeSouthEastCursor);
SEL s = @selector(_windowResizeSouthCursor);
SEL sw = @selector(_windowResizeSouthWestCursor);
SEL w = @selector(_windowResizeWestCursor);
SEL ns = @selector(_windowResizeNorthSouthCursor);
SEL ew = @selector(_windowResizeEastWestCursor);
SEL nwse = @selector(_windowResizeNorthWestSouthEastCursor);
SEL nesw = @selector(_windowResizeNorthEastSouthWestCursor);
BOOL match = YES;
if (dir.x != 0 && (isEqualNSCursor(cur, ew) || isEqualNSCursor(cur, w) || isEqualNSCursor(cur, e))) {
dir.y = 0;
}
if (dir.y != 0 && (isEqualNSCursor(cur, ns) || isEqualNSCursor(cur, s) || isEqualNSCursor(cur, n))) {
dir.x = 0;
}
// If none of the cursors matched, we may deduce that the resize
// direction is one of the corners. However, to ensure that at least
// one cursor matches, check the corner cursors.
if (dir.x == 1 && dir.y == 1) {
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, sw)) {
dir = NSZeroPoint;
}
} else if (dir.x == 1 && dir.y == -1) {
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, nw)) {
dir = NSZeroPoint;
}
} else if (dir.x == -1 && dir.y == 1) {
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, se)) {
dir = NSZeroPoint;
}
} else if (dir.x == -1 && dir.y == -1) {
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, ne)) {
dir = NSZeroPoint;
}
}
if (!NSEqualPoints(dir, NSZeroPoint)) {
NSEvent *cancel = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
location:l.locationInWindow
modifierFlags:l.modifierFlags
timestamp:l.timestamp
windowNumber:l.windowNumber
context:l.context
eventNumber:l.eventNumber
clickCount:l.clickCount
pressure:l.pressure];
resizeState.off = off;
resizeState.dir = dir;
return cancel;
}
}
}
return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:deqFlag];
}
@end
@interface GioWindow: NSWindow
@end
@implementation GioWindow
- (void)sendEvent:(NSEvent *)evt {
if (evt.type == NSEventTypeLeftMouseDown) {
resizeState.lastMouseDown = evt;
}
NSPoint dir = resizeState.dir;
if (NSEqualPoints(dir, NSZeroPoint)) {
[super sendEvent:evt];
return;
}
switch (evt.type) {
default:
return;
case NSEventTypeLeftMouseUp:
resizeState.dir = NSZeroPoint;
resizeState.lastMouseDown = nil;
return;
case NSEventTypeLeftMouseDragged:
// Ok to proceed.
break;
}
NSPoint loc = evt.locationInWindow;
NSPoint off = resizeState.off;
loc.x -= off.x;
loc.y -= off.y;
NSRect frame = [self frame];
NSSize min = [self minSize];
NSSize max = [self maxSize];
CGFloat width = frame.size.width;
if (dir.x > 0) {
width = loc.x;
} else if (dir.x < 0) {
width -= loc.x;
}
width = MIN(max.width, MAX(min.width, width));
if (dir.x < 0) {
frame.origin.x += frame.size.width - width;
}
frame.size.width = width;
CGFloat height = frame.size.height;
if (dir.y > 0) {
height = loc.y;
} else if (dir.y < 0) {
height -= loc.y;
}
height = MIN(max.height, MAX(min.height, height));
if (dir.y < 0) {
frame.origin.y += frame.size.height - height;
}
frame.size.height = height;
[self setFrame:frame display:YES animate:NO];
}
@end
@implementation GioView
- (void)setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize];
@@ -142,7 +348,6 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
// Don't pass commands up the responder chain.
// They will end up in a beep.
}
- (BOOL)hasMarkedText {
int res = gio_hasMarkedText(self.handle);
return res ? YES : NO;
@@ -255,14 +460,11 @@ void gio_showCursor() {
// some cursors are not public, this tries to use a private cursor
// and uses fallback when the use of private cursor fails.
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
if ([NSCursor respondsToSelector:cursorName]) {
id object = [NSCursor performSelector:cursorName];
if ([object isKindOfClass:[NSCursor class]]) {
[(NSCursor*)object set];
return;
}
NSCursor *cur = lookupPrivateNSCursor(cursorName);
if (cur == nil) {
cur = fallback;
}
[fallback set];
[cur set];
}
void gio_setCursor(NSUInteger curID) {
@@ -361,7 +563,7 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
NSMiniaturizableWindowMask |
NSClosableWindowMask;
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
GioWindow* window = [[GioWindow alloc] initWithContentRect:rect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:NO];
@@ -410,13 +612,24 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
gio_onFinishLaunching();
// Force the [NSApp run] call to return.
[NSApp stop:nil];
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSZeroPoint
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:dummy atStart:YES];
}
@end
void gio_main() {
void gio_initApp() {
@autoreleasepool {
[NSApplication sharedApplication];
[GioApplication sharedApplication];
GioAppDelegate *del = [[GioAppDelegate alloc] init];
[NSApp setDelegate:del];
@@ -438,6 +651,22 @@ void gio_main() {
globalWindowDel = [[GioWindowDelegate alloc] init];
// Runs until stopped by applicationDidFinishLaunching.
[NSApp run];
}
}
void gio_wakeupMainThread(void) {
@autoreleasepool {
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSZeroPoint
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:dummy atStart:YES];
}
}
-4
View File
@@ -33,10 +33,6 @@ type WaylandViewEvent struct {
func (WaylandViewEvent) implementsViewEvent() {}
func (WaylandViewEvent) ImplementsEvent() {}
func osMain() {
select {}
}
type windowDriver func(*callbacks, []Option) error
// Instead of creating files with build tags for each combination of wayland +/- x11
+4 -2
View File
@@ -1222,7 +1222,8 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
w := callbackLoad(unsafe.Pointer(surf)).(*window)
s.keyboardFocus = w
s.disp.repeat.Stop(0)
w.ProcessEvent(key.FocusEvent{Focus: true})
w.config.Focused = true
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export gio_onKeyboardLeave
@@ -1231,7 +1232,8 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
s.serial = serial
s.disp.repeat.Stop(0)
w := s.keyboardFocus
w.ProcessEvent(key.FocusEvent{Focus: false})
w.config.Focused = false
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export gio_onKeyboardKey
+5 -10
View File
@@ -50,7 +50,6 @@ type window struct {
placement *windows.WindowPlacement
animating bool
focused bool
borderSize image.Point
config Config
@@ -86,10 +85,6 @@ var resources struct {
cursor syscall.Handle
}
func osMain() {
select {}
}
func newWindow(win *callbacks, options []Option) {
done := make(chan struct{})
go func() {
@@ -269,11 +264,11 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
Kind: pointer.Cancel,
})
case windows.WM_SETFOCUS:
w.focused = true
w.ProcessEvent(key.FocusEvent{Focus: true})
w.config.Focused = true
w.ProcessEvent(ConfigEvent{Config: w.config})
case windows.WM_KILLFOCUS:
w.focused = false
w.ProcessEvent(key.FocusEvent{Focus: false})
w.config.Focused = false
w.ProcessEvent(ConfigEvent{Config: w.config})
case windows.WM_NCHITTEST:
if w.config.Decorated {
// Let the system handle it.
@@ -496,7 +491,7 @@ func (w *window) hitTest(x, y int) uintptr {
}
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
if !w.focused {
if !w.config.Focused {
windows.SetFocus(w.hwnd)
}
+4 -2
View File
@@ -657,9 +657,11 @@ func (h *x11EventHandler) handleEvents() bool {
// redraw only on the last expose event
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
case C.FocusIn:
w.ProcessEvent(key.FocusEvent{Focus: true})
w.config.Focused = true
w.ProcessEvent(ConfigEvent{Config: w.config})
case C.FocusOut:
w.ProcessEvent(key.FocusEvent{Focus: false})
w.config.Focused = false
w.ProcessEvent(ConfigEvent{Config: w.config})
case C.ConfigureNotify: // window configuration change
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
+1 -1
View File
@@ -25,6 +25,6 @@ func runMain() {
// Indirect call, since the linker does not know the address of main when
// laying down this package.
fn := mainMain
fn()
go fn()
})
}
+17
View File
@@ -630,9 +630,19 @@ func (w *Window) processEvent(e event.Event) bool {
}
w.coalesced.view = &e2
case ConfigEvent:
wasFocused := w.decorations.Config.Focused
w.decorations.Config = e2.Config
e2.Config = w.effectiveConfig()
w.coalesced.cfg = &e2
if f := w.decorations.Config.Focused; f != wasFocused {
w.queue.Queue(key.FocusEvent{Focus: f})
}
t, handled := w.queue.WakeupTime()
if handled {
w.setNextFrame(t)
w.updateAnimation()
}
return handled
case event.Event:
focusDir := key.FocusDirection(-1)
if e, ok := e2.(key.Event); ok && e.State == key.Press {
@@ -674,6 +684,13 @@ func (w *Window) processEvent(e event.Event) bool {
// Event blocks until an event is received from the window, such as
// [FrameEvent], or until [Invalidate] is called.
//
// Note: if more than one Window is active, at least one must have
// its Event called from the main goroutine that runs the main
// function. This is necessary because some operating system GUI
// implementations require control of the main thread.
// For this reason, it is allowed to call Event even after a
// DestroyEvent has been received.
func (w *Window) Event() event.Event {
w.init()
return w.basic.Event()