7 Commits

Author SHA1 Message Date
Chris Waldon 1802761c93 go.*: update go-text
This picks up some improvements to face splitting and line wrapping within the
text stack.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-03-29 13:29:03 -04:00
Chris Waldon 0558bb3f1c widget: update test expectations
This commit fixes our tests to expect some whitespace-handling changes in upstream
go-text.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-03-29 13:29:03 -04:00
Benoit KUGLER 78ce5e3ad5 deps: bump go-text/typesetting version to v0.1.0
Using this stable release should ensure user upgrading gio with go get -u do not encouter compilation error

Signed-off-by: Benoit KUGLER <benoit.kugler@gmail.com>
2024-03-29 13:29:03 -04:00
Aman Karmani 1be34eec6f app: [tvOS] fix build failures
Fixes: https://todo.sr.ht/~eliasnaur/gio/567
Signed-off-by: Aman Karmani <aman@tmm1.net>
2024-03-06 21:49:42 +00:00
Elias Naur 44ede4eceb app: update documentation for Window.Run
Window events are no longer asynchronous, so deadlocks are no longer
possible when calling Run.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-03-06 21:36:00 +00:00
Elias Naur 993ec907be app: introduce Config.Focused that tracks the window focus state
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-03-06 20:49:44 +00:00
Elias Naur 35785e9c96 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-03-06 20:49:44 +00:00
18 changed files with 150 additions and 371 deletions
+12
View File
@@ -120,6 +120,18 @@ func DataDir() (string, error) {
return dataDir() 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 (FrameEvent) ImplementsEvent() {}
func init() { func init() {
+21 -5
View File
@@ -33,12 +33,28 @@ For example:
A program must keep receiving events from the event channel until A program must keep receiving events from the event channel until
[DestroyEvent] is received. [DestroyEvent] is received.
# Main Thread # Main
Some GUI platform need access to the main thread of the program. To avoid a The Main function must be called from a program's main function, to hand over
deadlock on such platforms, at least one Window must have its Event method control of the main thread to operating systems that need it.
called by the main goroutine. It doesn't have to be any particular Window;
even a destroyed Window suffices. 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()
}
# Permissions # Permissions
+3
View File
@@ -1317,6 +1317,9 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
return C.jni_FindClass(env, cn) return C.jni_FindClass(env, cn)
} }
func osMain() {
}
func newWindow(window *callbacks, options []Option) { func newWindow(window *callbacks, options []Option) {
mainWindow.in <- windowAndConfig{window, options} mainWindow.in <- windowAndConfig{window, options}
<-mainWindow.windows <-mainWindow.windows
+10 -7
View File
@@ -15,8 +15,8 @@ __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
__attribute__ ((visibility ("hidden"))) void gio_showCursor(); __attribute__ ((visibility ("hidden"))) void gio_showCursor();
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID); __attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
static int isMainThread() { static bool isMainThread() {
return [NSThread isMainThread] ? 1 : 0; return [NSThread isMainThread];
} }
static NSUInteger nsstringLength(CFTypeRef cstr) { static NSUInteger nsstringLength(CFTypeRef cstr) {
@@ -77,7 +77,7 @@ var mainFuncs = make(chan func(), 1)
// runOnMain runs the function on the main thread. // runOnMain runs the function on the main thread.
func runOnMain(f func()) { func runOnMain(f func()) {
if isMainThread() { if C.isMainThread() {
f() f()
return return
} }
@@ -87,10 +87,6 @@ func runOnMain(f func()) {
}() }()
} }
func isMainThread() bool {
return C.isMainThread() != 0
}
//export gio_dispatchMainFuncs //export gio_dispatchMainFuncs
func gio_dispatchMainFuncs() { func gio_dispatchMainFuncs() {
for { for {
@@ -263,3 +259,10 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
C.gio_setCursor(C.NSUInteger(macosCursorID[to])) C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
return to return to
} }
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
w.loop.FlushEvents()
})
}
+11
View File
@@ -0,0 +1,11 @@
// 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 -7
View File
@@ -21,6 +21,7 @@ struct drawParams {
}; };
static void writeClipboard(unichar *chars, NSUInteger length) { static void writeClipboard(unichar *chars, NSUInteger length) {
#if !TARGET_OS_TV
@autoreleasepool { @autoreleasepool {
NSString *s = [NSString string]; NSString *s = [NSString string];
if (length > 0) { if (length > 0) {
@@ -29,13 +30,18 @@ static void writeClipboard(unichar *chars, NSUInteger length) {
UIPasteboard *p = UIPasteboard.generalPasteboard; UIPasteboard *p = UIPasteboard.generalPasteboard;
p.string = s; p.string = s;
} }
#endif
} }
static CFTypeRef readClipboard(void) { static CFTypeRef readClipboard(void) {
#if !TARGET_OS_TV
@autoreleasepool { @autoreleasepool {
UIPasteboard *p = UIPasteboard.generalPasteboard; UIPasteboard *p = UIPasteboard.generalPasteboard;
return (__bridge_retained CFTypeRef)p.string; return (__bridge_retained CFTypeRef)p.string;
} }
#else
return nil;
#endif
} }
static void showTextInput(CFTypeRef viewRef) { static void showTextInput(CFTypeRef viewRef) {
@@ -388,17 +394,13 @@ func newWindow(win *callbacks, options []Option) {
<-mainWindow.windows <-mainWindow.windows
} }
func osMain() {
}
//export gio_runMain //export gio_runMain
func gio_runMain() { func gio_runMain() {
runMain() runMain()
} }
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
w.loop.FlushEvents()
})
}
func (UIKitViewEvent) implementsViewEvent() {} func (UIKitViewEvent) implementsViewEvent() {}
func (UIKitViewEvent) ImplementsEvent() {} func (UIKitViewEvent) ImplementsEvent() {}
+5 -7
View File
@@ -26,12 +26,13 @@ CGFloat _keyboardHeight;
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0); self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame]; UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
[self.view addSubview: drawView]; [self.view addSubview: drawView];
#ifndef TARGET_OS_TV #if !TARGET_OS_TV
drawView.multipleTouchEnabled = YES; drawView.multipleTouchEnabled = YES;
#endif #endif
drawView.preservesSuperviewLayoutMargins = YES; drawView.preservesSuperviewLayoutMargins = YES;
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0); drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self); onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
#if !TARGET_OS_TV
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillChange:) selector:@selector(keyboardWillChange:)
name:UIKeyboardWillShowNotification name:UIKeyboardWillShowNotification
@@ -44,6 +45,7 @@ CGFloat _keyboardHeight;
selector:@selector(keyboardWillHide:) selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification name:UIKeyboardWillHideNotification
object:nil]; object:nil];
#endif
[[NSNotificationCenter defaultCenter] addObserver: self [[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(applicationDidEnterBackground:) selector: @selector(applicationDidEnterBackground:)
name: UIApplicationDidEnterBackgroundNotification name: UIApplicationDidEnterBackgroundNotification
@@ -89,6 +91,7 @@ CGFloat _keyboardHeight;
[super didReceiveMemoryWarning]; [super didReceiveMemoryWarning];
} }
#if !TARGET_OS_TV
- (void)keyboardWillChange:(NSNotification *)note { - (void)keyboardWillChange:(NSNotification *)note {
NSDictionary *userInfo = note.userInfo; NSDictionary *userInfo = note.userInfo;
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
@@ -100,6 +103,7 @@ CGFloat _keyboardHeight;
_keyboardHeight = 0.0; _keyboardHeight = 0.0;
[self.view setNeedsLayout]; [self.view setNeedsLayout];
} }
#endif
@end @end
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) { static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
@@ -276,9 +280,3 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
GioView *v = (__bridge GioView *)viewRef; GioView *v = (__bridge GioView *)viewRef;
v.handle = handle; v.handle = handle;
} }
void gio_wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_dispatchMainFuncs();
});
}
+4
View File
@@ -741,6 +741,10 @@ func (w *window) navigationColor(c color.NRGBA) {
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B})) theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
} }
func osMain() {
select {}
}
func translateKey(k string) (key.Name, bool) { func translateKey(k string) (key.Name, bool) {
var n key.Name var n key.Name
+32 -74
View File
@@ -39,7 +39,7 @@ import (
#define MOUSE_DOWN 3 #define MOUSE_DOWN 3
#define MOUSE_SCROLL 4 #define MOUSE_SCROLL 4
__attribute__ ((visibility ("hidden"))) void gio_initApp(void); __attribute__ ((visibility ("hidden"))) void gio_main(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(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"))) 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); __attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
@@ -195,6 +195,14 @@ static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w,
} }
} }
static void resetLayerFrame(CFTypeRef viewRef) {
@autoreleasepool {
NSView* view = (__bridge NSView *)viewRef;
NSRect r = view.frame;
view.layer.frame = r;
}
}
static void hideWindow(CFTypeRef windowRef) { static void hideWindow(CFTypeRef windowRef) {
@autoreleasepool { @autoreleasepool {
NSWindow* window = (__bridge NSWindow *)windowRef; NSWindow* window = (__bridge NSWindow *)windowRef;
@@ -289,16 +297,6 @@ 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" import "C"
@@ -333,16 +331,13 @@ type window struct {
config Config config Config
} }
// launched is closed after gio_initApp returns. // launched is closed when applicationDidFinishLaunching is called.
var launched = make(chan struct{}) var launched = make(chan struct{})
// nextTopLeft is the offset to use for the next window's call to // nextTopLeft is the offset to use for the next window's call to
// cascadeTopLeftFromPoint. // cascadeTopLeftFromPoint.
var nextTopLeft C.NSPoint 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 { func windowFor(h C.uintptr_t) *window {
return cgo.Handle(h).Value().(*window) return cgo.Handle(h).Value().(*window)
} }
@@ -469,6 +464,9 @@ func (w *window) Configure(options []Option) {
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans) C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans) C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, 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}) w.ProcessEvent(ConfigEvent{Config: w.config})
} }
@@ -835,51 +833,23 @@ func (w *window) draw() {
func (w *window) ProcessEvent(e event.Event) { func (w *window) ProcessEvent(e event.Event) {
w.w.ProcessEvent(e) w.w.ProcessEvent(e)
// The main thread window deliver events in Event. w.loop.FlushEvents()
if w != mainThreadWindow {
w.loop.FlushEvents()
}
} }
func (w *window) Event() event.Event { func (w *window) Event() event.Event {
if !isMainThread() { return w.loop.Event()
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() { func (w *window) Invalidate() {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Invalidate() w.loop.Invalidate()
} }
func (w *window) Run(f func()) { func (w *window) Run(f func()) {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Run(f) w.loop.Run(f)
} }
func (w *window) Frame(frame *op.Ops) { func (w *window) Frame(frame *op.Ops) {
if !isMainThread() { w.loop.Frame(frame)
w.loop.Frame(frame)
return
}
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
w.loop.win.ProcessFrame(frame, nil)
} }
func configFor(scale float32) unit.Metric { func configFor(scale float32) unit.Metric {
@@ -939,27 +909,20 @@ func gio_onWindowed(h C.uintptr_t) {
w.ProcessEvent(ConfigEvent{Config: w.config}) w.ProcessEvent(ConfigEvent{Config: w.config})
} }
//export gio_onFinishLaunching
func gio_onFinishLaunching() {
close(launched)
}
func newWindow(win *callbacks, options []Option) { func newWindow(win *callbacks, options []Option) {
w := &window{ <-launched
redraw: make(chan struct{}, 1), res := make(chan struct{})
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)
}
} else {
<-launched
}
res := make(chan struct{}, 1)
runOnMain(func() { runOnMain(func() {
w := &window{
redraw: make(chan struct{}, 1),
w: win,
}
w.loop = newEventLoop(w.w, w.wakeup)
win.SetDriver(w) win.SetDriver(w)
res <- struct{}{} res <- struct{}{}
if err := w.init(); err != nil { if err := w.init(); err != nil {
@@ -1012,6 +975,10 @@ func (w *window) init() error {
return nil return nil
} }
func osMain() {
C.gio_main()
}
func convertKey(k rune) (key.Name, bool) { func convertKey(k rune) (key.Name, bool) {
var n key.Name var n key.Name
switch k { switch k {
@@ -1096,14 +1063,5 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
return kmods return kmods
} }
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
if w != mainThreadWindow {
w.loop.FlushEvents()
}
})
}
func (AppKitViewEvent) implementsViewEvent() {} func (AppKitViewEvent) implementsViewEvent() {}
func (AppKitViewEvent) ImplementsEvent() {} func (AppKitViewEvent) ImplementsEvent() {}
+12 -241
View File
@@ -69,212 +69,6 @@ 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]); 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 @implementation GioView
- (void)setFrameSize:(NSSize)newSize { - (void)setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize]; [super setFrameSize:newSize];
@@ -348,6 +142,7 @@ static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
// Don't pass commands up the responder chain. // Don't pass commands up the responder chain.
// They will end up in a beep. // They will end up in a beep.
} }
- (BOOL)hasMarkedText { - (BOOL)hasMarkedText {
int res = gio_hasMarkedText(self.handle); int res = gio_hasMarkedText(self.handle);
return res ? YES : NO; return res ? YES : NO;
@@ -460,11 +255,14 @@ void gio_showCursor() {
// some cursors are not public, this tries to use a private cursor // some cursors are not public, this tries to use a private cursor
// and uses fallback when the use of private cursor fails. // and uses fallback when the use of private cursor fails.
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) { static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
NSCursor *cur = lookupPrivateNSCursor(cursorName); if ([NSCursor respondsToSelector:cursorName]) {
if (cur == nil) { id object = [NSCursor performSelector:cursorName];
cur = fallback; if ([object isKindOfClass:[NSCursor class]]) {
[(NSCursor*)object set];
return;
}
} }
[cur set]; [fallback set];
} }
void gio_setCursor(NSUInteger curID) { void gio_setCursor(NSUInteger curID) {
@@ -563,7 +361,7 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
NSMiniaturizableWindowMask | NSMiniaturizableWindowMask |
NSClosableWindowMask; NSClosableWindowMask;
GioWindow* window = [[GioWindow alloc] initWithContentRect:rect NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
styleMask:styleMask styleMask:styleMask
backing:NSBackingStoreBuffered backing:NSBackingStoreBuffered
defer:NO]; defer:NO];
@@ -612,24 +410,13 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
// Force the [NSApp run] call to return. gio_onFinishLaunching();
[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 @end
void gio_initApp() { void gio_main() {
@autoreleasepool { @autoreleasepool {
[GioApplication sharedApplication]; [NSApplication sharedApplication];
GioAppDelegate *del = [[GioAppDelegate alloc] init]; GioAppDelegate *del = [[GioAppDelegate alloc] init];
[NSApp setDelegate:del]; [NSApp setDelegate:del];
@@ -651,22 +438,6 @@ void gio_initApp() {
globalWindowDel = [[GioWindowDelegate alloc] init]; globalWindowDel = [[GioWindowDelegate alloc] init];
// Runs until stopped by applicationDidFinishLaunching.
[NSApp run]; [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,6 +33,10 @@ type WaylandViewEvent struct {
func (WaylandViewEvent) implementsViewEvent() {} func (WaylandViewEvent) implementsViewEvent() {}
func (WaylandViewEvent) ImplementsEvent() {} func (WaylandViewEvent) ImplementsEvent() {}
func osMain() {
select {}
}
type windowDriver func(*callbacks, []Option) error type windowDriver func(*callbacks, []Option) error
// Instead of creating files with build tags for each combination of wayland +/- x11 // Instead of creating files with build tags for each combination of wayland +/- x11
+4
View File
@@ -85,6 +85,10 @@ var resources struct {
cursor syscall.Handle cursor syscall.Handle
} }
func osMain() {
select {}
}
func newWindow(win *callbacks, options []Option) { func newWindow(win *callbacks, options []Option) {
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
+1 -1
View File
@@ -25,6 +25,6 @@ func runMain() {
// Indirect call, since the linker does not know the address of main when // Indirect call, since the linker does not know the address of main when
// laying down this package. // laying down this package.
fn := mainMain fn := mainMain
go fn() fn()
}) })
} }
+1 -11
View File
@@ -302,10 +302,7 @@ func (w *Window) Option(opts ...Option) {
} }
// Run f in the same thread as the native window event loop, and wait for f to // Run f in the same thread as the native window event loop, and wait for f to
// return or the window to close. Run is guaranteed not to deadlock if it is // return or the window to close.
// invoked during the handling of a [ViewEvent], [FrameEvent],
// [StageEvent]; call Run in a separate goroutine to avoid deadlock in all
// other cases.
// //
// Note that most programs should not call Run; configuring a Window with // Note that most programs should not call Run; configuring a Window with
// [CustomRenderer] is a notable exception. // [CustomRenderer] is a notable exception.
@@ -684,13 +681,6 @@ func (w *Window) processEvent(e event.Event) bool {
// Event blocks until an event is received from the window, such as // Event blocks until an event is received from the window, such as
// [FrameEvent], or until [Invalidate] is called. // [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 { func (w *Window) Event() event.Event {
w.init() w.init()
return w.basic.Event() return w.basic.Event()
+3 -3
View File
@@ -6,11 +6,11 @@ require (
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
gioui.org/shader v1.0.8 gioui.org/shader v1.0.8
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 github.com/go-text/typesetting v0.1.1
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
golang.org/x/image v0.5.0 golang.org/x/image v0.5.0
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 golang.org/x/sys v0.5.0
) )
require golang.org/x/text v0.7.0 require golang.org/x/text v0.9.0
+7 -6
View File
@@ -5,9 +5,9 @@ gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJG
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo= github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -28,15 +28,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+3 -1
View File
@@ -4,6 +4,7 @@ package text
import ( import (
"bytes" "bytes"
"fmt"
"image" "image"
"io" "io"
"log" "log"
@@ -276,7 +277,8 @@ func newShaperImpl(systemFonts bool, collection []FontFace) *shaperImpl {
// It returns whether the face is now available for use. FontFaces are prioritized // It returns whether the face is now available for use. FontFaces are prioritized
// in the order in which they are loaded, with the first face being the default. // in the order in which they are loaded, with the first face being the default.
func (s *shaperImpl) Load(f FontFace) { func (s *shaperImpl) Load(f FontFace) {
s.fontMap.AddFace(f.Face.Face(), opentype.FontToDescription(f.Font)) desc := opentype.FontToDescription(f.Font)
s.fontMap.AddFace(f.Face.Face(), fontscan.Location{File: fmt.Sprint(desc)}, desc)
s.addFace(f.Face.Face(), f.Font) s.addFace(f.Face.Face(), f.Font)
} }
+8 -8
View File
@@ -284,9 +284,9 @@ func TestIndexPositionBidi(t *testing.T) {
name: "bidi rtl", name: "bidi rtl",
glyphs: bidiRTLText, glyphs: bidiRTLText,
expectedXs: []fixed.Int26_6{ expectedXs: []fixed.Int26_6{
2665, 3291, 3861, 4431, 4716, 5286, 5856, 6109, 6621, 7133, 2665, 2380, 1577, 985, 687, 266, // Positions on line 0. 2646, 3272, 3842, 4412, 4697, 5267, 5837, 6090, 6602, 7114, 2646, 2380, 1577, 985, 687, 266, // Positions on line 0.
7886, 7118, 6350, 5582, 4814, 4529, 4231, 3933, 3667, 2300, 2585, 3155, 3667, 2300, 2015, 1709, 1117, 266, // Positions on line 1. 7867, 7099, 6331, 5563, 4795, 4510, 4212, 3914, 3648, 2281, 2566, 3136, 3648, 2281, 2015, 1709, 1117, 266, // Positions on line 1.
8794, 8026, 7258, 6490, 5722, 5437, 4922, 4540, 4134, 3868, 0, 290, 860, 1430, 1715, 1989, 2559, 3071, 3583, // Positions on line 2. 8794, 8026, 7258, 6490, 5722, 5437, 4922, 4540, 4134, 3868, 0, 290, 860, 1430, 1715, 1989, 2559, 3071, 3583, // Positions on line 2.
@@ -402,7 +402,7 @@ func TestIndexPositionLines(t *testing.T) {
xOff: fixed.Int26_6(0), xOff: fixed.Int26_6(0),
yOff: 22, yOff: 22,
glyphs: 15, glyphs: 15,
width: fixed.Int26_6(7133), width: fixed.Int26_6(7114),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
@@ -410,7 +410,7 @@ func TestIndexPositionLines(t *testing.T) {
xOff: fixed.Int26_6(0), xOff: fixed.Int26_6(0),
yOff: 41, yOff: 41,
glyphs: 15, glyphs: 15,
width: fixed.Int26_6(7886), width: fixed.Int26_6(7867),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
@@ -477,18 +477,18 @@ func TestIndexPositionLines(t *testing.T) {
glyphs: bidiRTLTextOpp, glyphs: bidiRTLTextOpp,
expectedLines: []lineInfo{ expectedLines: []lineInfo{
{ {
xOff: fixed.Int26_6(3107), xOff: fixed.Int26_6(3126),
yOff: 22, yOff: 22,
glyphs: 15, glyphs: 15,
width: fixed.Int26_6(7133), width: fixed.Int26_6(7114),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(2354), xOff: fixed.Int26_6(2373),
yOff: 41, yOff: 41,
glyphs: 15, glyphs: 15,
width: fixed.Int26_6(7886), width: fixed.Int26_6(7867),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },