app: add support for system cursors

Signed-off-by: pierre <pierre.curto@gmail.com>
This commit is contained in:
pierre
2020-12-02 16:50:56 +01:00
committed by Elias Naur
parent 951d45a2c6
commit 69cc2c795c
15 changed files with 297 additions and 18 deletions
+2
View File
@@ -3,6 +3,8 @@ image: freebsd/11.x
packages:
- libX11
- libxkbcommon
- libXcursor
- libXfixes
- wayland
- mesa-libs
- xorg-vfbserver
+6 -1
View File
@@ -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();
private static boolean jniLoaded;
private final SurfaceHolder.Callback surfCallbacks;
@@ -124,6 +124,11 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
return true;
}
private void setCursor(Context ctx, int id) {
PointerIcon pointerIcon = PointerIcon.getSystemIcon(ctx, id);
GioView.this.setPointerIcon(pointerIcon);
}
private void dispatchMotionEvent(MotionEvent event) {
for (int j = 0; j < event.getHistorySize(); j++) {
long time = event.getHistoricalEventTime(j);
+28
View File
@@ -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 = 1000 // TYPE_ARROW
case pointer.CursorText:
curID = 1008 // TYPE_TEXT
case pointer.CursorPointer:
curID = 1002 // TYPE_HAND
case pointer.CursorCrossHair:
curID = 1007 // TYPE_CROSSHAIR
case pointer.CursorColResize:
curID = 1014 // TYPE_HORIZONTAL_DOUBLE_ARROW
case pointer.CursorRowResize:
curID = 1015 // TYPE_VERTICAL_DOUBLE_ARROW
case pointer.CursorNone:
curID = 0 // TYPE_NULL
}
runOnMain(func(env *C.JNIEnv) {
callVoidMethod(env, w.view, w.msetCursor,
jvalue(android.appCtx), jvalue(curID))
})
}
// Close the window. Not implemented for Android.
func (w *window) Close() {}
+43
View File
@@ -13,6 +13,9 @@ __attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
__attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
__attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did);
__attribute__ ((visibility ("hidden"))) void gio_hideCursor();
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
*/
import "C"
import (
@@ -22,6 +25,8 @@ import (
"time"
"unicode/utf16"
"unsafe"
"gioui.org/io/pointer"
)
// displayLink is the state for a display link (CVDisplayLinkRef on macOS,
@@ -169,3 +174,41 @@ func gio_onFrameCallback(dl C.CFTypeRef) {
}
}
}
// windowSetCursor updates the cursor from the current one to a new one
// and returns the new one.
func windowSetCursor(from, to pointer.CursorName) pointer.CursorName {
if from == to {
return to
}
var curID int
switch to {
default:
to = 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:
runOnMain(func() {
C.gio_hideCursor()
})
return to
}
runOnMain(func() {
if from == pointer.CursorNone {
C.gio_showCursor()
}
C.gio_setCursor(C.NSUInteger(curID))
})
return to
}
+40
View File
@@ -20,3 +20,43 @@ void gio_nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, N
NSString *str = (__bridge NSString *)cstr;
[str getCharacters:chars range:NSMakeRange(loc, length)];
}
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;
}
}
}
+5
View File
@@ -51,6 +51,7 @@ type window struct {
layer C.CFTypeRef
visible atomic.Value
cursor pointer.CursorName
pointerMap []C.CFTypeRef
}
@@ -249,6 +250,10 @@ func (w *window) SetAnimating(anim bool) {
}
}
func (w *window) SetCursor(name pointer.CursorName) {
w.cursor = windowSetCursor(w.cursor, name)
}
func (w *window) onKeyCommand(name string) {
w.w.Event(key.Event{
Name: name,
+5
View File
@@ -426,6 +426,11 @@ func (w *window) WriteClipboard(s string) {
w.clipboard.Call("writeText", s)
}
func (w *window) SetCursor(name pointer.CursorName) {
style := w.cnv.Get("style")
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.
+7 -1
View File
@@ -58,6 +58,7 @@ type window struct {
w Callbacks
stage system.Stage
displayLink *displayLink
cursor pointer.CursorName
scale float32
}
@@ -74,7 +75,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 +123,10 @@ func (w *window) WriteClipboard(s string) {
})
}
func (w *window) SetCursor(name pointer.CursorName) {
w.cursor = windowSetCursor(w.cursor, name)
}
func (w *window) ShowTextInput(show bool) {}
func (w *window) SetAnimating(anim bool) {
@@ -227,6 +232,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
+45 -10
View File
@@ -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{}
+39 -3
View File
@@ -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 {
+26 -2
View File
@@ -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.
+4
View File
@@ -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()
}
+12 -1
View File
@@ -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 {
+8
View File
@@ -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.
//
+27
View File
@@ -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() {}