~pierrec: 1 app: add system cursor support 17 files changed, 422 insertions(+), 30 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/15410/mbox | git am -3Learn more about email & git
From: pierre <pierre.curto@gmail.com> Signed-off-by: pierre <pierre.curto@gmail.com> --- .builds/freebsd.yml | 2 ++ .builds/linux.yml | 2 +- app/internal/window/GioView.java | 41 ++++++++++++++++++++++- app/internal/window/os_android.go | 28 ++++++++++++++++ app/internal/window/os_ios.go | 39 ++++++++++++++++++++++ app/internal/window/os_ios.m | 40 ++++++++++++++++++++++ app/internal/window/os_js.go | 17 ++++++++++ app/internal/window/os_macos.go | 42 ++++++++++++++++++++++- app/internal/window/os_macos.m | 40 ++++++++++++++++++++++ app/internal/window/os_wayland.go | 55 +++++++++++++++++++++++++------ app/internal/window/os_windows.go | 42 +++++++++++++++++++++-- app/internal/window/os_x11.go | 28 ++++++++++++++-- app/internal/window/window.go | 4 +++ app/internal/windows/windows.go | 13 +++++++- app/window.go | 8 +++++ example/kitchen/main_test.go | 24 +++++++------- io/pointer/pointer.go | 27 +++++++++++++++ 17 files changed, 422 insertions(+), 30 deletions(-) diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 7ad7b5d..ee76afa 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -3,6 +3,8 @@ image: freebsd/11.x packages: - libX11 - libxkbcommon + - libXcursor + - libXfixes
Thanks. What about OpenBSD?
- wayland - mesa-libs - xorg-vfbserver diff --git a/.builds/linux.yml b/.builds/linux.yml index cc45d7d..77b12ef 100644 --- a/.builds/linux.yml +++ b/.builds/linux.yml @@ -84,7 +84,7 @@ tasks: cd gio for hash in $(git log -n 20 --format="%H"); do message=$(git log -1 --format=%B $hash) - if [[ ! "$message" =~ "Signed-off-by: " ]]; then + if [[ ! "$message" =~ "Merge branch" ]] && [[ ! "$message" =~ "Signed-off-by: " ]]; then
What's the reason for this change? I apply changes by rebasing, never by merging.
echo "unsigned commit $hash" exit 1 fi diff --git a/app/internal/window/GioView.java b/app/internal/window/GioView.java index a55346a..fd8b7b9 100644 --- a/app/internal/window/GioView.java +++ b/app/internal/window/GioView.java @@ -22,6 +22,7 @@ import android.view.Choreographer; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; import android.view.WindowInsets; @@ -36,7 +37,6 @@ import android.view.inputmethod.EditorInfo; import java.io.UnsupportedEncodingException; public final class GioView extends SurfaceView implements Choreographer.FrameCallback { - private final static Object initLock = new Object();
Thanks!
private static boolean jniLoaded; private final SurfaceHolder.Callback surfCallbacks; @@ -45,6 +45,9 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal private final float scrollXScale; private final float scrollYScale; + private static final Object pointerIconLock = new Object(); + private PointerIcon pointerIcon; + private long nhandle; public GioView(Context context) { @@ -124,7 +127,43 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal return true; } + private void setCursor(Context ctx, int curID) { + int id; + switch (curID) { + case 0: + id = PointerIcon.TYPE_NULL; + break; + case 1: + id = PointerIcon.TYPE_ARROW; + break; + case 2: + id = PointerIcon.TYPE_TEXT; + break; + case 3: + id = PointerIcon.TYPE_HAND; + break; + case 4: + id = PointerIcon.TYPE_CROSSHAIR; + break; + case 5: + id = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; + break; + case 6: + id = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; + break; + default: + id = PointerIcon.TYPE_ARROW; + break; + } + synchronized (pointerIconLock) { + pointerIcon = PointerIcon.getSystemIcon(ctx, id); + }
Use tabs everywhere. Javafmt sure would be nice :)
+ } + private void dispatchMotionEvent(MotionEvent event) { + synchronized (pointerIconLock) { + GioView.this.setPointerIcon(pointerIcon); + }
Why use a lock and delayed setting of the pointer? Isn't runOnMain enough?
for (int j = 0; j < event.getHistorySize(); j++) { long time = event.getHistoricalEventTime(j); for (int i = 0; i < event.getPointerCount(); i++) { diff --git a/app/internal/window/os_android.go b/app/internal/window/os_android.go index fc494e5..f796d81 100644 --- a/app/internal/window/os_android.go +++ b/app/internal/window/os_android.go @@ -80,6 +80,7 @@ type window struct { mshowTextInput C.jmethodID mhideTextInput C.jmethodID mpostFrameCallback C.jmethodID + msetCursor C.jmethodID } // ViewEvent is sent whenever the Window's underlying Android view @@ -202,6 +203,7 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j mshowTextInput: getMethodID(env, class, "showTextInput", "()V"), mhideTextInput: getMethodID(env, class, "hideTextInput", "()V"), mpostFrameCallback: getMethodID(env, class, "postFrameCallback", "()V"), + msetCursor: getMethodID(env, class, "setCursor", "(Landroid/content/Context;I)V"), } wopts := <-mainWindow.out w.callbacks = wopts.window @@ -649,6 +651,32 @@ func (w *window) ReadClipboard() { }) } +func (w *window) SetCursor(name pointer.CursorName) { + var curID int + switch name { + default: + fallthrough + case pointer.CursorDefault: + curID = 1 + case pointer.CursorText: + curID = 2 + case pointer.CursorPointer: + curID = 3 + case pointer.CursorCrossHair: + curID = 4 + case pointer.CursorColResize: + curID = 5 + case pointer.CursorRowResize: + curID = 6 + case pointer.CursorNone: + curID = 0 + } + runInJVM(javaVM(), func(env *C.JNIEnv) {
runOnMain and avoid the lock and delayed set?
+ callVoidMethod(env, w.view, w.msetCursor, + jvalue(android.appCtx), jvalue(curID)) + }) +} + // Close the window. Not implemented for Android. func (w *window) Close() {} diff --git a/app/internal/window/os_ios.go b/app/internal/window/os_ios.go index 1865454..cfd72ba 100644 --- a/app/internal/window/os_ios.go +++ b/app/internal/window/os_ios.go @@ -22,6 +22,9 @@ __attribute__ ((visibility ("hidden"))) void gio_hideTextInput(CFTypeRef viewRef __attribute__ ((visibility ("hidden"))) void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef); __attribute__ ((visibility ("hidden"))) void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef); __attribute__ ((visibility ("hidden"))) void gio_removeLayer(CFTypeRef layerRef); +__attribute__ ((visibility ("hidden"))) void gio_hideCursor(); +__attribute__ ((visibility ("hidden"))) void gio_showCursor(); +__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID); __attribute__ ((visibility ("hidden"))) struct drawParams gio_viewDrawParams(CFTypeRef viewRef); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void); __attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length); @@ -51,6 +54,7 @@ type window struct { layer C.CFTypeRef visible atomic.Value + cursor pointer.CursorName pointerMap []C.CFTypeRef } @@ -249,6 +253,41 @@ func (w *window) SetAnimating(anim bool) { } } +func (w *window) SetCursor(name pointer.CursorName) { + var curID int + switch name { + default: + name = pointer.CursorDefault + fallthrough + case pointer.CursorDefault: + curID = 1 + case pointer.CursorText: + curID = 2 + case pointer.CursorPointer: + curID = 3 + case pointer.CursorCrossHair: + curID = 4 + case pointer.CursorColResize: + curID = 5 + case pointer.CursorRowResize: + curID = 6 + case pointer.CursorNone: + w.cursor = name + runOnMain(func() { + C.gio_hideCursor() + }) + return + } + hidden := w.cursor == pointer.CursorNone + w.cursor = name + runOnMain(func() { + if hidden { + C.gio_showCursor() + } + C.gio_setCursor(C.NSUInteger(curID)) + }) +} + func (w *window) onKeyCommand(name string) { w.w.Event(key.Event{ Name: name, diff --git a/app/internal/window/os_ios.m b/app/internal/window/os_ios.m index 66830e4..973842e 100644 --- a/app/internal/window/os_ios.m +++ b/app/internal/window/os_ios.m
The iOS code looks very similar to the macOS code. Is it possible to move the common parts to os_darwin.go|m and avoid the duplication?
@@ -247,6 +247,46 @@ CFTypeRef gio_readClipboard(void) { } } +void gio_hideCursor() { + @autoreleasepool { + [NSCursor hide]; + } +} + +void gio_showCursor() { + @autoreleasepool { + [NSCursor unhide]; + } +} + +void gio_setCursor(NSUInteger curID) { + @autoreleasepool { + switch (curID) { + case 1: + [NSCursor.arrowCursor set]; + break; + case 2: + [NSCursor.IBeamCursor set]; + break; + case 3: + [NSCursor.pointingHandCursor set]; + break; + case 4: + [NSCursor.crosshairCursor set]; + break; + case 5: + [NSCursor.resizeLeftRightCursor set]; + break; + case 6: + [NSCursor.resizeUpDownCursor set]; + break; + default: + [NSCursor.arrowCursor set]; + break; + } + } +} + void gio_showTextInput(CFTypeRef viewRef) { UIView *view = (__bridge UIView *)viewRef; [view becomeFirstResponder]; diff --git a/app/internal/window/os_js.go b/app/internal/window/os_js.go index e4fb17c..246a55a 100644 --- a/app/internal/window/os_js.go +++ b/app/internal/window/os_js.go @@ -426,6 +426,23 @@ func (w *window) WriteClipboard(s string) { w.clipboard.Call("writeText", s) } +func (w *window) SetCursor(name pointer.CursorName) { + doc := js.Global().Get("document") + cont := doc.Call("getElementsByTagName", "body")
Why not set the cursor on w.cnv? At the very least, setting the cursor on the body will cause conflicts if multiple Gio programs are running on the same page (see https://gioui.org/doc/architecture).
+ if cont.IsNull() { + return + } + body := cont.Get("0") + if body.IsNull() { + return + } + style := body.Get("style") + if style.IsNull() { + return + } + style.Set("cursor", string(name)) +} + func (w *window) ShowTextInput(show bool) { // Run in a goroutine to avoid a deadlock if the // focus change result in an event. diff --git a/app/internal/window/os_macos.go b/app/internal/window/os_macos.go index 1afec6f..72f2fdd 100644 --- a/app/internal/window/os_macos.go +++ b/app/internal/window/os_macos.go @@ -38,6 +38,9 @@ __attribute__ ((visibility ("hidden"))) CGFloat gio_viewHeight(CFTypeRef viewRef __attribute__ ((visibility ("hidden"))) CGFloat gio_getViewBackingScale(CFTypeRef viewRef); __attribute__ ((visibility ("hidden"))) CGFloat gio_getScreenBackingScale(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void); +__attribute__ ((visibility ("hidden"))) void gio_hideCursor(); +__attribute__ ((visibility ("hidden"))) void gio_showCursor(); +__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID); __attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length); __attribute__ ((visibility ("hidden"))) void gio_setNeedsDisplay(CFTypeRef viewRef); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight); @@ -58,6 +61,7 @@ type window struct { w Callbacks stage system.Stage displayLink *displayLink + cursor pointer.CursorName scale float32 } @@ -74,7 +78,7 @@ var launched = make(chan struct{}) // cascadeTopLeftFromPoint. var nextTopLeft C.NSPoint -// mustView is like lookoupView, except that it panics +// mustView is like lookupView, except that it panics // if the view isn't mapped. func mustView(view C.CFTypeRef) *window { w, ok := lookupView(view) @@ -122,6 +126,41 @@ func (w *window) WriteClipboard(s string) { }) } +func (w *window) SetCursor(name pointer.CursorName) { + var curID int + switch name { + default: + name = pointer.CursorDefault + fallthrough + case pointer.CursorDefault: + curID = 1 + case pointer.CursorText: + curID = 2 + case pointer.CursorPointer: + curID = 3 + case pointer.CursorCrossHair: + curID = 4 + case pointer.CursorColResize: + curID = 5 + case pointer.CursorRowResize: + curID = 6 + case pointer.CursorNone: + w.cursor = name + runOnMain(func() { + C.gio_hideCursor() + }) + return + } + hidden := w.cursor == pointer.CursorNone + w.cursor = name + runOnMain(func() { + if hidden { + C.gio_showCursor() + } + C.gio_setCursor(C.NSUInteger(curID)) + }) +} + func (w *window) ShowTextInput(show bool) {} func (w *window) SetAnimating(anim bool) { @@ -227,6 +266,7 @@ func gio_onDraw(view C.CFTypeRef) { func gio_onFocus(view C.CFTypeRef, focus C.BOOL) { w := mustView(view) w.w.Event(key.FocusEvent{Focus: focus == C.YES}) + w.SetCursor(w.cursor) } //export gio_onChangeScreen diff --git a/app/internal/window/os_macos.m b/app/internal/window/os_macos.m index b8c0dee..3dddcaa 100644 --- a/app/internal/window/os_macos.m +++ b/app/internal/window/os_macos.m @@ -67,6 +67,46 @@ CFTypeRef gio_readClipboard(void) { } } +void gio_hideCursor() { + @autoreleasepool { + [NSCursor hide]; + } +} + +void gio_showCursor() { + @autoreleasepool { + [NSCursor unhide]; + } +} + +void gio_setCursor(NSUInteger curID) { + @autoreleasepool { + switch (curID) { + case 1: + [NSCursor.arrowCursor set]; + break; + case 2: + [NSCursor.IBeamCursor set]; + break; + case 3: + [NSCursor.pointingHandCursor set]; + break; + case 4: + [NSCursor.crosshairCursor set]; + break; + case 5: + [NSCursor.resizeLeftRightCursor set]; + break; + case 6: + [NSCursor.resizeUpDownCursor set]; + break; + default: + [NSCursor.arrowCursor set]; + break; + } + } +} + CGFloat gio_viewHeight(CFTypeRef viewRef) { NSView *view = (__bridge NSView *)viewRef; return [view bounds].size.height; diff --git a/app/internal/window/os_wayland.go b/app/internal/window/os_wayland.go index 60f59b1..59d1071 100644 --- a/app/internal/window/os_wayland.go +++ b/app/internal/window/os_wayland.go @@ -764,16 +764,7 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria s.serial = serial w := callbackLoad(unsafe.Pointer(surf)).(*window) s.pointerFocus = w - // Get images[0]. - img := *w.cursor.cursor.images - buf := C.wl_cursor_image_get_buffer(img) - if buf == nil { - return - } - C.wl_pointer_set_cursor(pointer, serial, w.cursor.surf, C.int32_t(img.hotspot_x), C.int32_t(img.hotspot_y)) - C.wl_surface_attach(w.cursor.surf, buf, 0, 0) - C.wl_surface_damage(w.cursor.surf, 0, 0, C.int32_t(img.width), C.int32_t(img.height)) - C.wl_surface_commit(w.cursor.surf) + w.setCursor(pointer, serial) w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)} } @@ -917,6 +908,50 @@ func (w *window) WriteClipboard(s string) { w.disp.wakeup() } +func (w *window) SetCursor(name pointer.CursorName) { + if name == pointer.CursorNone { + C.wl_pointer_set_cursor(w.disp.seat.pointer, w.serial, nil, 0, 0) + return + } + switch name { + default: + fallthrough + case pointer.CursorDefault: + name = "left_ptr" + case pointer.CursorText: + name = "xterm" + case pointer.CursorPointer: + name = "hand1" + case pointer.CursorCrossHair: + name = "crosshair" + case pointer.CursorRowResize: + name = "top_side" + case pointer.CursorColResize: + name = "left_side" + } + cname := C.CString(string(name)) + defer C.free(unsafe.Pointer(cname)) + c := C.wl_cursor_theme_get_cursor(w.cursor.theme, cname) + if c == nil { + return + } + w.cursor.cursor = c + w.setCursor(w.disp.seat.pointer, w.serial) +} + +func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) { + // Get images[0]. + img := *w.cursor.cursor.images + buf := C.wl_cursor_image_get_buffer(img) + if buf == nil { + return + } + C.wl_pointer_set_cursor(pointer, serial, w.cursor.surf, C.int32_t(img.hotspot_x), C.int32_t(img.hotspot_y)) + C.wl_surface_attach(w.cursor.surf, buf, 0, 0) + C.wl_surface_damage(w.cursor.surf, 0, 0, C.int32_t(img.width), C.int32_t(img.height)) + C.wl_surface_commit(w.cursor.surf) +} + func (w *window) resetFling() { w.fling.start = false w.fling.anim = fling.Animation{} diff --git a/app/internal/window/os_windows.go b/app/internal/window/os_windows.go index 2a38d53..ae706f9 100644 --- a/app/internal/window/os_windows.go +++ b/app/internal/window/os_windows.go @@ -45,6 +45,7 @@ type window struct { height int stage system.Stage pointerBtns pointer.Buttons + cursor syscall.Handle mu sync.Mutex animating bool @@ -105,6 +106,9 @@ func NewWindow(window Callbacks, opts *Options) error { windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT) windows.SetForegroundWindow(w.hwnd) windows.SetFocus(w.hwnd) + // Since the window class for the cursor is null, + // set it here to show the cursor. + w.SetCursor(pointer.CursorDefault) if err := w.loop(); err != nil { panic(err) } @@ -120,17 +124,16 @@ func initResources() error { return err } resources.handle = hInst - curs, err := windows.LoadCursor(windows.IDC_ARROW) + c, err := loadCursor(pointer.CursorDefault) if err != nil { return err } - resources.cursor = curs + resources.cursor = c wcls := windows.WndClassEx{ CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})), Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC, LpfnWndProc: syscall.NewCallback(windowProc), HInstance: hInst, - HCursor: curs, LpszClassName: syscall.StringToUTF16Ptr("GioWindow"), } cls, err := windows.RegisterClassEx(&wcls) @@ -299,6 +302,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr Y: w.minmax.maxHeight + w.deltas.height, } } + case windows.WM_SETCURSOR: + windows.SetCursor(w.cursor) } return windows.DefWindowProc(hwnd, msg, wParam, lParam) @@ -552,6 +557,37 @@ func (w *window) writeClipboard(s string) error { return nil } +func (w *window) SetCursor(name pointer.CursorName) { + c, err := loadCursor(name) + if err != nil { + c = resources.cursor + } + w.cursor = c +} + +func loadCursor(name pointer.CursorName) (syscall.Handle, error) { + var curID uint16 + switch name { + default: + fallthrough + case pointer.CursorDefault: + return resources.cursor, nil + case pointer.CursorText: + curID = windows.IDC_IBEAM + case pointer.CursorPointer: + curID = windows.IDC_HAND + case pointer.CursorCrossHair: + curID = windows.IDC_CROSS + case pointer.CursorColResize: + curID = windows.IDC_SIZEWE + case pointer.CursorRowResize: + curID = windows.IDC_SIZENS + case pointer.CursorNone: + return 0, nil + } + return windows.LoadCursor(curID) +} + func (w *window) ShowTextInput(show bool) {} func (w *window) HDC() syscall.Handle { diff --git a/app/internal/window/os_x11.go b/app/internal/window/os_x11.go index 413a959..ae8fb51 100644 --- a/app/internal/window/os_x11.go +++ b/app/internal/window/os_x11.go @@ -7,8 +7,8 @@ package window /* #cgo openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include #cgo openbsd LDFLAGS: -L/usr/X11R6/lib -L/usr/local/lib -#cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb -#cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb +#cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb -lXcursor -lXfixes +#cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb xcursor xfixes #include <stdlib.h> #include <locale.h> @@ -18,6 +18,8 @@ package window #include <X11/Xresource.h> #include <X11/XKBlib.h> #include <X11/Xlib-xcb.h> +#include <X11/extensions/Xfixes.h> +#include <X11/Xcursor/Xcursor.h> #include <xkbcommon/xkbcommon-x11.h> */ @@ -87,6 +89,7 @@ type x11Window struct { write *string content []byte } + cursor pointer.CursorName } func (w *x11Window) SetAnimating(anim bool) { @@ -112,6 +115,27 @@ func (w *x11Window) WriteClipboard(s string) { w.wakeup() } +func (w *x11Window) SetCursor(name pointer.CursorName) { + if name == pointer.CursorNone { + w.cursor = name + C.XFixesHideCursor(w.x, w.xw) + return + } + if w.cursor == pointer.CursorNone { + C.XFixesShowCursor(w.x, w.xw) + } + cname := C.CString(string(name)) + defer C.free(unsafe.Pointer(cname)) + c := C.XcursorLibraryLoadCursor(w.x, cname) + if c == 0 { + name = pointer.CursorDefault + } + w.cursor = name + // If c if null (i.e. name was not found), + // XDefineCursor will use the default cursor. + C.XDefineCursor(w.x, w.xw, c) +} + func (w *x11Window) ShowTextInput(show bool) {} // Close the window. diff --git a/app/internal/window/window.go b/app/internal/window/window.go index 30ba360..f140214 100644 --- a/app/internal/window/window.go +++ b/app/internal/window/window.go @@ -9,6 +9,7 @@ import ( "gioui.org/gpu/backend" "gioui.org/io/event" + "gioui.org/io/pointer" "gioui.org/io/system" "gioui.org/unit" ) @@ -60,6 +61,9 @@ type Driver interface { // WriteClipboard requests a clipboard write. WriteClipboard(s string) + // SetCursor updates the current cursor to name. + SetCursor(name pointer.CursorName) + // Close the window. Close() } diff --git a/app/internal/windows/windows.go b/app/internal/windows/windows.go index 21b1858..22435b4 100644 --- a/app/internal/windows/windows.go +++ b/app/internal/windows/windows.go @@ -61,7 +61,12 @@ const ( CW_USEDEFAULT = -2147483648 - IDC_ARROW = 32512 + IDC_ARROW = 32512 + IDC_IBEAM = 32513 + IDC_HAND = 32649 + IDC_CROSS = 32515 + IDC_SIZENS = 32645 + IDC_SIZEWE = 32644 INFINITE = 0xFFFFFFFF @@ -146,6 +151,7 @@ const ( WM_PAINT = 0x000F WM_CLOSE = 0x0010 WM_QUIT = 0x0012 + WM_SETCURSOR = 0x0020 WM_SETFOCUS = 0x0007 WM_KILLFOCUS = 0x0008 WM_SHOWWINDOW = 0x0018 @@ -227,6 +233,7 @@ var ( _ScreenToClient = user32.NewProc("ScreenToClient") _ShowWindow = user32.NewProc("ShowWindow") _SetCapture = user32.NewProc("SetCapture") + _SetCursor = user32.NewProc("SetCursor") _SetClipboardData = user32.NewProc("SetClipboardData") _SetForegroundWindow = user32.NewProc("SetForegroundWindow") _SetFocus = user32.NewProc("SetFocus") @@ -514,6 +521,10 @@ func SetClipboardData(format uint32, mem syscall.Handle) error { return nil } +func SetCursor(h syscall.Handle) { + _SetCursor.Call(uintptr(h)) +} + func SetTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc uintptr) error { r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), uintptr(uElapse), timerProc) if r == 0 { diff --git a/app/window.go b/app/window.go index ed46958..136350a 100644 --- a/app/window.go +++ b/app/window.go @@ -10,6 +10,7 @@ import ( "gioui.org/app/internal/window" "gioui.org/io/event" + "gioui.org/io/pointer" "gioui.org/io/profile" "gioui.org/io/router" "gioui.org/io/system" @@ -209,6 +210,13 @@ func (w *Window) WriteClipboard(s string) { }) } +// SetCursorName changes the current window cursor to name. +func (w *Window) SetCursorName(name pointer.CursorName) { + go w.driverDo(func() { + w.driver.SetCursor(name) + }) +} + // Close the window. The window's event loop should exit when it receives // system.DestroyEvent. // diff --git a/example/kitchen/main_test.go b/example/kitchen/main_test.go index 91a1130..c3c16e5 100644 --- a/example/kitchen/main_test.go +++ b/example/kitchen/main_test.go @@ -7,19 +7,21 @@ import ( "testing" "time" - "gioui.org/layout" - "gioui.org/op" "gioui.org/app/headless" "gioui.org/f32" "gioui.org/font/gofont" + "gioui.org/layout" + "gioui.org/op" "gioui.org/widget/material" ) -func BenchmarkUI(b *testing.B) { benchmarkUI(b, transformation{}) } +func BenchmarkUI(b *testing.B) { benchmarkUI(b, transformation{}) } func BenchmarkUI_Offset(b *testing.B) { benchmarkUI(b, transformation{offset: true}) } -func BenchmarkUI_Scale(b *testing.B) { benchmarkUI(b, transformation{scale: true}) } +func BenchmarkUI_Scale(b *testing.B) { benchmarkUI(b, transformation{scale: true}) } func BenchmarkUI_Rotate(b *testing.B) { benchmarkUI(b, transformation{rotate: true}) } -func BenchmarkUI_All(b *testing.B) { benchmarkUI(b, transformation{offset: true, rotate: true, scale: true}) } +func BenchmarkUI_All(b *testing.B) { + benchmarkUI(b, transformation{offset: true, rotate: true, scale: true}) +} func benchmarkUI(b *testing.B, transform transformation) { th := material.NewTheme(gofont.Collection()) @@ -42,19 +44,19 @@ func benchmarkUI(b *testing.B, transform transformation) { Constraints: layout.Exact(image.Pt(800, 600)), } addTransform(i, transform, gtx.Ops) - layoutTime += measure(func(){ kitchen(gtx, th) }) - frameTime += measure(func(){ w.Frame(&ops) }) + layoutTime += measure(func() { kitchen(gtx, th) }) + frameTime += measure(func() { w.Frame(&ops) }) } b.StopTimer() - b.ReportMetric(float64(layoutTime.Nanoseconds()) / float64(b.N), "ns/layout") - b.ReportMetric(float64(frameTime.Nanoseconds()) / float64(b.N), "ns/frame") + b.ReportMetric(float64(layoutTime.Nanoseconds())/float64(b.N), "ns/layout") + b.ReportMetric(float64(frameTime.Nanoseconds())/float64(b.N), "ns/frame") } type transformation struct { offset bool rotate bool - scale bool + scale bool } func addTransform(i int, transform transformation, ops *op.Ops) { @@ -88,4 +90,4 @@ func measure(fn func()) time.Duration { start := time.Now() fn() return time.Since(start) -} \ No newline at end of file +}
Unrelated changes, please drop. I pushed this change to master in any case: gioui.org/commit/a792dd5553c916528bfb.
diff --git a/io/pointer/pointer.go b/io/pointer/pointer.go index 2c0010a..a5b6c2c 100644 --- a/io/pointer/pointer.go +++ b/io/pointer/pointer.go @@ -79,9 +79,29 @@ type Source uint8 // Buttons is a set of mouse buttons type Buttons uint8 +// CursorName is the name of a cursor. +type CursorName string + // Must match app/internal/input.areaKind type areaKind uint8 +const ( + // CursorDefault is the default cursor. + CursorDefault CursorName = "" + // CursorText is the cursor for text. + CursorText CursorName = "text" + // CursorPointer is the cursor for a link. + CursorPointer CursorName = "pointer" + // CursorCrossHair is the cursor for precise location. + CursorCrossHair CursorName = "crosshair" + // CursorColResize is the cursor for vertical resize. + CursorColResize CursorName = "col-resize" + // CursorRowResize is the cursor for horizontal resize. + CursorRowResize CursorName = "row-resize" + // CursorNone hides the cursor. To show it again, use any other cursor. + CursorNone CursorName = "none" +) + const ( // A Cancel event is generated when the current gesture is // interrupted by other handlers or the system. @@ -242,4 +262,11 @@ func (b Buttons) String() string { return strings.Join(strs, "|") } +func (c CursorName) String() string { + if c == CursorDefault { + return "default" + } + return string(c) +} + func (Event) ImplementsEvent() {} -- 2.26.2