Added Android custom cursor implementation
authorSam Lantinga <slouken@libsdl.org>
Thu, 15 Mar 2018 18:22:48 -0700
changeset 11931d479188e0155
parent 11930 c808a74c019e
child 11932 a05a909eeddb
Added Android custom cursor implementation
This is commented out in SDLActivity.java, with the note #CURSORIMPLEENTATION because it requires API 24, which is higher than the minimum required SDK
android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
src/core/android/SDL_android.c
src/core/android/SDL_android.h
src/video/android/SDL_androidmouse.c
test/testcustomcursor.c
     1.1 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java	Mon Mar 12 18:41:06 2018 -0700
     1.2 +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java	Thu Mar 15 18:22:48 2018 -0700
     1.3 @@ -3,6 +3,7 @@
     1.4  import java.io.IOException;
     1.5  import java.io.InputStream;
     1.6  import java.util.Arrays;
     1.7 +import java.util.Hashtable;
     1.8  import java.lang.reflect.Method;
     1.9  
    1.10  import android.app.*;
    1.11 @@ -37,6 +38,21 @@
    1.12  
    1.13      public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
    1.14  
    1.15 +    // Cursor types
    1.16 +    private static final int SDL_SYSTEM_CURSOR_NONE = -1;
    1.17 +    private static final int SDL_SYSTEM_CURSOR_ARROW = 0;
    1.18 +    private static final int SDL_SYSTEM_CURSOR_IBEAM = 1;
    1.19 +    private static final int SDL_SYSTEM_CURSOR_WAIT = 2;
    1.20 +    private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3;
    1.21 +    private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4;
    1.22 +    private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5;
    1.23 +    private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6;
    1.24 +    private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7;
    1.25 +    private static final int SDL_SYSTEM_CURSOR_SIZENS = 8;
    1.26 +    private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9;
    1.27 +    private static final int SDL_SYSTEM_CURSOR_NO = 10;
    1.28 +    private static final int SDL_SYSTEM_CURSOR_HAND = 11;
    1.29 +
    1.30      // Handle the state of the native layer
    1.31      public enum NativeState {
    1.32             INIT, RESUMED, PAUSED
    1.33 @@ -61,6 +77,10 @@
    1.34      protected static boolean mScreenKeyboardShown;
    1.35      protected static ViewGroup mLayout;
    1.36      protected static SDLClipboardHandler mClipboardHandler;
    1.37 +    //#CURSORIMPLEENTATION
    1.38 +    //protected static Hashtable<Integer, PointerIcon> mCursors;
    1.39 +    //protected static int mLastCursorID;
    1.40 +    //protected static PointerIcon mActiveCursor;
    1.41  
    1.42  
    1.43      // This is what SDL runs in. It invokes SDL_main(), eventually
    1.44 @@ -133,6 +153,10 @@
    1.45          mTextEdit = null;
    1.46          mLayout = null;
    1.47          mClipboardHandler = null;
    1.48 +        //#CURSORIMPLEENTATION
    1.49 +        //mCursors = new Hashtable<Integer, PointerIcon>();
    1.50 +        //mLastCursorID = 0;
    1.51 +        //mActiveCursor = null;
    1.52          mSDLThread = null;
    1.53          mExitCalledFromJava = false;
    1.54          mBrokenLibraries = false;
    1.55 @@ -1051,7 +1075,7 @@
    1.56      public static boolean clipboardHasText() {
    1.57          return mClipboardHandler.clipboardHasText();
    1.58      }
    1.59 -    
    1.60 +
    1.61      /**
    1.62       * This method is called by SDL using JNI.
    1.63       */
    1.64 @@ -1065,6 +1089,73 @@
    1.65      public static void clipboardSetText(String string) {
    1.66          mClipboardHandler.clipboardSetText(string);
    1.67      }
    1.68 +
    1.69 +    /**
    1.70 +     * This method is called by SDL using JNI.
    1.71 +     */
    1.72 +    /**
    1.73 +     * #CURSORIMPLEENTATION
    1.74 +     * The cursor implementation requires API 24 or above
    1.75 +     *
    1.76 +    public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {
    1.77 +        Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
    1.78 +        ++mLastCursorID;
    1.79 +        mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY));
    1.80 +        return mLastCursorID;
    1.81 +    }
    1.82 +
    1.83 +    public static void setCustomCursor(int cursorID) {
    1.84 +        mActiveCursor = mCursors.get(cursorID);
    1.85 +    }
    1.86 +
    1.87 +    public static void setSystemCursor(int cursorID) {
    1.88 +        switch (cursorID) {
    1.89 +        case SDL_SYSTEM_CURSOR_NONE:
    1.90 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_NULL);
    1.91 +            break;
    1.92 +        case SDL_SYSTEM_CURSOR_ARROW:
    1.93 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_ARROW);
    1.94 +            break;
    1.95 +        case SDL_SYSTEM_CURSOR_IBEAM:
    1.96 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_TEXT);
    1.97 +            break;
    1.98 +        case SDL_SYSTEM_CURSOR_WAIT:
    1.99 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_WAIT);
   1.100 +            break;
   1.101 +        case SDL_SYSTEM_CURSOR_CROSSHAIR:
   1.102 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_CROSSHAIR);
   1.103 +            break;
   1.104 +        case SDL_SYSTEM_CURSOR_WAITARROW:
   1.105 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_WAIT);
   1.106 +            break;
   1.107 +        case SDL_SYSTEM_CURSOR_SIZENWSE:
   1.108 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW);
   1.109 +            break;
   1.110 +        case SDL_SYSTEM_CURSOR_SIZENESW:
   1.111 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW);
   1.112 +            break;
   1.113 +        case SDL_SYSTEM_CURSOR_SIZEWE:
   1.114 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW);
   1.115 +            break;
   1.116 +        case SDL_SYSTEM_CURSOR_SIZENS:
   1.117 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW);
   1.118 +            break;
   1.119 +        case SDL_SYSTEM_CURSOR_SIZEALL:
   1.120 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_GRAB);
   1.121 +            break;
   1.122 +        case SDL_SYSTEM_CURSOR_NO:
   1.123 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_NO_DROP);
   1.124 +            break;
   1.125 +        case SDL_SYSTEM_CURSOR_HAND:
   1.126 +            mActiveCursor = PointerIcon.getSystemIcon(SDL.getContext(), PointerIcon.TYPE_HAND);
   1.127 +            break;
   1.128 +        }
   1.129 +    }
   1.130 +
   1.131 +    public static PointerIcon getCursor() {
   1.132 +        return mActiveCursor;
   1.133 +    }
   1.134 +    */
   1.135  }
   1.136  
   1.137  /**
   1.138 @@ -1456,6 +1547,14 @@
   1.139                                        event.values[2] / SensorManager.GRAVITY_EARTH);
   1.140          }
   1.141      }
   1.142 +
   1.143 +    /**
   1.144 +     * #CURSORIMPLEENTATION
   1.145 +    @Override
   1.146 +    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
   1.147 +        return SDLActivity.getCursor();
   1.148 +    }
   1.149 +    */
   1.150  }
   1.151  
   1.152  /* This is a fake invisible editor view that receives the input and defines the
     2.1 --- a/src/core/android/SDL_android.c	Mon Mar 12 18:41:06 2018 -0700
     2.2 +++ b/src/core/android/SDL_android.c	Thu Mar 15 18:22:48 2018 -0700
     2.3 @@ -223,6 +223,9 @@
     2.4  static jmethodID midOpenAPKExpansionInputStream;
     2.5  static jmethodID midGetManifestEnvironmentVariables;
     2.6  static jmethodID midGetDisplayDPI;
     2.7 +static jmethodID midCreateCustomCursor;
     2.8 +static jmethodID midSetCustomCursor;
     2.9 +static jmethodID midSetSystemCursor;
    2.10  
    2.11  /* audio manager */
    2.12  static jclass mAudioManagerClass;
    2.13 @@ -332,7 +335,11 @@
    2.14                                  "getManifestEnvironmentVariables", "()Z");
    2.15  
    2.16      midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
    2.17 -    midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
    2.18 +
    2.19 +    /* Custom cursor implementation is only available on API 24 and above */
    2.20 +    midCreateCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "createCustomCursor", "([IIIII)I");
    2.21 +    midSetCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setCustomCursor", "(I)V");
    2.22 +    midSetSystemCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setSystemCursor", "(I)V");
    2.23  
    2.24      if (!midGetNativeSurface ||
    2.25         !midSetActivityTitle || !midSetWindowStyle || !midSetOrientation || !midGetContext || !midIsAndroidTV || !midInputGetInputDeviceIds ||
    2.26 @@ -2167,6 +2174,40 @@
    2.27      }
    2.28  }
    2.29  
    2.30 +int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y)
    2.31 +{
    2.32 +    JNIEnv *mEnv = Android_JNI_GetEnv();
    2.33 +    int custom_cursor = 0;
    2.34 +    if (midCreateCustomCursor) {
    2.35 +        jintArray pixels;
    2.36 +        pixels = (*mEnv)->NewIntArray(mEnv, surface->w * surface->h);
    2.37 +        if (!pixels) {
    2.38 +            return 0;
    2.39 +        }
    2.40 +        (*mEnv)->SetIntArrayRegion(mEnv, pixels, 0, surface->w * surface->h, (int *)surface->pixels);
    2.41 +        custom_cursor = (*mEnv)->CallStaticIntMethod(mEnv, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y);
    2.42 +        (*mEnv)->DeleteLocalRef(mEnv, pixels);
    2.43 +    }
    2.44 +    return custom_cursor;
    2.45 +}
    2.46 +
    2.47 +
    2.48 +void Android_JNI_SetCustomCursor(int cursorID)
    2.49 +{
    2.50 +    JNIEnv *mEnv = Android_JNI_GetEnv();
    2.51 +    if (midSetCustomCursor) {
    2.52 +        (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetCustomCursor, cursorID);
    2.53 +    }
    2.54 +}
    2.55 +
    2.56 +void Android_JNI_SetSystemCursor(int cursorID)
    2.57 +{
    2.58 +    JNIEnv *mEnv = Android_JNI_GetEnv();
    2.59 +    if (midSetSystemCursor) {
    2.60 +        (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetSystemCursor, cursorID);
    2.61 +    }
    2.62 +}
    2.63 +
    2.64  #endif /* __ANDROID__ */
    2.65  
    2.66  /* vi: set ts=4 sw=4 expandtab: */
     3.1 --- a/src/core/android/SDL_android.h	Mon Mar 12 18:41:06 2018 -0700
     3.2 +++ b/src/core/android/SDL_android.h	Thu Mar 15 18:22:48 2018 -0700
     3.3 @@ -102,6 +102,11 @@
     3.4  #include "SDL_messagebox.h"
     3.5  int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid);
     3.6  
     3.7 +/* Cursor support */
     3.8 +int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y);
     3.9 +void Android_JNI_SetCustomCursor(int cursorID);
    3.10 +void Android_JNI_SetSystemCursor(int cursorID);
    3.11 +
    3.12  /* Ends C function definitions when using C++ */
    3.13  #ifdef __cplusplus
    3.14  /* *INDENT-OFF* */
     4.1 --- a/src/video/android/SDL_androidmouse.c	Mon Mar 12 18:41:06 2018 -0700
     4.2 +++ b/src/video/android/SDL_androidmouse.c	Thu Mar 15 18:22:48 2018 -0700
     4.3 @@ -42,12 +42,107 @@
     4.4  #define BUTTON_BACK 8
     4.5  #define BUTTON_FORWARD 16
     4.6  
     4.7 +typedef struct
     4.8 +{
     4.9 +    int custom_cursor;
    4.10 +    int system_cursor;
    4.11 +
    4.12 +} SDL_AndroidCursorData;
    4.13 +
    4.14  /* Last known Android mouse button state (includes all buttons) */
    4.15  static int last_state;
    4.16  
    4.17 +
    4.18 +static SDL_Cursor *
    4.19 +Android_WrapCursor(int custom_cursor, int system_cursor)
    4.20 +{
    4.21 +    SDL_Cursor *cursor;
    4.22 +
    4.23 +    cursor = SDL_calloc(1, sizeof(*cursor));
    4.24 +    if (cursor) {
    4.25 +        SDL_AndroidCursorData *data = (SDL_AndroidCursorData*)SDL_calloc(1, sizeof(*data));
    4.26 +        if (data) {
    4.27 +            data->custom_cursor = custom_cursor;
    4.28 +            data->system_cursor = system_cursor;
    4.29 +            cursor->driverdata = data;
    4.30 +        } else {
    4.31 +            SDL_free(cursor);
    4.32 +            cursor = NULL;
    4.33 +            SDL_OutOfMemory();
    4.34 +        }
    4.35 +    } else {
    4.36 +        SDL_OutOfMemory();
    4.37 +    }
    4.38 +
    4.39 +    return cursor;
    4.40 +}
    4.41 +
    4.42 +static SDL_Cursor *
    4.43 +Android_CreateDefaultCursor()
    4.44 +{
    4.45 +    return Android_WrapCursor(0, SDL_SYSTEM_CURSOR_ARROW);
    4.46 +}
    4.47 +
    4.48 +static SDL_Cursor *
    4.49 +Android_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
    4.50 +{
    4.51 +    int custom_cursor;
    4.52 +    SDL_Surface *converted;
    4.53 +
    4.54 +    converted = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0);
    4.55 +    if (!converted) {
    4.56 +        return NULL;
    4.57 +    }
    4.58 +    custom_cursor = Android_JNI_CreateCustomCursor(converted, hot_x, hot_y);
    4.59 +    SDL_FreeSurface(converted);
    4.60 +    if (!custom_cursor) {
    4.61 +        SDL_Unsupported();
    4.62 +        return NULL;
    4.63 +    }
    4.64 +    return Android_WrapCursor(custom_cursor, 0);
    4.65 +}
    4.66 +
    4.67 +static SDL_Cursor *
    4.68 +Android_CreateSystemCursor(SDL_SystemCursor id)
    4.69 +{
    4.70 +    return Android_WrapCursor(0, id);
    4.71 +}
    4.72 +
    4.73 +static void
    4.74 +Android_FreeCursor(SDL_Cursor * cursor)
    4.75 +{
    4.76 +    SDL_free(cursor->driverdata);
    4.77 +    SDL_free(cursor);
    4.78 +}
    4.79 +
    4.80 +static int
    4.81 +Android_ShowCursor(SDL_Cursor * cursor)
    4.82 +{
    4.83 +    if (cursor) {
    4.84 +        SDL_AndroidCursorData *data = (SDL_AndroidCursorData*)cursor->driverdata;
    4.85 +        if (data->custom_cursor) {
    4.86 +            Android_JNI_SetCustomCursor(data->custom_cursor);
    4.87 +        } else {
    4.88 +            Android_JNI_SetSystemCursor(data->system_cursor);
    4.89 +        }
    4.90 +    } else {
    4.91 +        Android_JNI_SetSystemCursor(-1);
    4.92 +    }
    4.93 +    return 0;
    4.94 +}
    4.95 +
    4.96  void
    4.97  Android_InitMouse(void)
    4.98  {
    4.99 +    SDL_Mouse *mouse = SDL_GetMouse();
   4.100 +
   4.101 +    mouse->CreateCursor = Android_CreateCursor;
   4.102 +    mouse->CreateSystemCursor = Android_CreateSystemCursor;
   4.103 +    mouse->ShowCursor = Android_ShowCursor;
   4.104 +    mouse->FreeCursor = Android_FreeCursor;
   4.105 +
   4.106 +    SDL_SetDefaultCursor(Android_CreateDefaultCursor());
   4.107 +
   4.108      last_state = 0;
   4.109  }
   4.110  
     5.1 --- a/test/testcustomcursor.c	Mon Mar 12 18:41:06 2018 -0700
     5.2 +++ b/test/testcustomcursor.c	Thu Mar 15 18:22:48 2018 -0700
     5.3 @@ -73,6 +73,24 @@
     5.4      SDL_Cursor *cursor = NULL;
     5.5      SDL_Surface *surface = SDL_LoadBMP(file);
     5.6      if (surface) {
     5.7 +        if (surface->format->palette) {
     5.8 +            SDL_SetColorKey(surface, 1, *(Uint8 *) surface->pixels);
     5.9 +        } else {
    5.10 +            switch (surface->format->BitsPerPixel) {
    5.11 +            case 15:
    5.12 +                SDL_SetColorKey(surface, 1, (*(Uint16 *)surface->pixels) & 0x00007FFF);
    5.13 +                break;
    5.14 +            case 16:
    5.15 +                SDL_SetColorKey(surface, 1, *(Uint16 *)surface->pixels);
    5.16 +                break;
    5.17 +            case 24:
    5.18 +                SDL_SetColorKey(surface, 1, (*(Uint32 *)surface->pixels) & 0x00FFFFFF);
    5.19 +                break;
    5.20 +            case 32:
    5.21 +                SDL_SetColorKey(surface, 1, *(Uint32 *)surface->pixels);
    5.22 +                break;
    5.23 +            }
    5.24 +        }
    5.25          cursor = SDL_CreateColorCursor(surface, 0, 0);
    5.26          SDL_FreeSurface(surface);
    5.27      }
    5.28 @@ -116,7 +134,9 @@
    5.29  
    5.30  static SDLTest_CommonState *state;
    5.31  int done;
    5.32 -SDL_Cursor *cursor = NULL;
    5.33 +static SDL_Cursor *cursors[1+SDL_NUM_SYSTEM_CURSORS];
    5.34 +static int current_cursor;
    5.35 +static int show_cursor;
    5.36  
    5.37  /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
    5.38  static void
    5.39 @@ -134,6 +154,18 @@
    5.40      /* Check for events */
    5.41      while (SDL_PollEvent(&event)) {
    5.42          SDLTest_CommonEvent(state, &event, &done);
    5.43 +        if (event.type == SDL_MOUSEBUTTONDOWN) {
    5.44 +            if (event.button.button == SDL_BUTTON_LEFT) {
    5.45 +                ++current_cursor;
    5.46 +                if (current_cursor == SDL_arraysize(cursors)) {
    5.47 +                    current_cursor = 0;
    5.48 +                }
    5.49 +                SDL_SetCursor(cursors[current_cursor]);
    5.50 +            } else {
    5.51 +                show_cursor = !show_cursor;
    5.52 +                SDL_ShowCursor(show_cursor);
    5.53 +            }
    5.54 +        }
    5.55      }
    5.56      
    5.57      for (i = 0; i < state->num_windows; ++i) {
    5.58 @@ -188,15 +220,22 @@
    5.59      }
    5.60  
    5.61      if (color_cursor) {
    5.62 -        cursor = init_color_cursor(color_cursor);
    5.63 +        cursors[0] = init_color_cursor(color_cursor);
    5.64      } else {
    5.65 -        cursor = init_system_cursor(arrow);
    5.66 +        cursors[0] = init_system_cursor(arrow);
    5.67      }
    5.68 -    if (!cursor) {
    5.69 +    if (!cursors[0]) {
    5.70          SDL_Log("Error, couldn't create cursor\n");
    5.71          quit(2);
    5.72      }
    5.73 -    SDL_SetCursor(cursor);
    5.74 +    for (i = 0; i < SDL_NUM_SYSTEM_CURSORS; ++i) {
    5.75 +        cursors[1+i] = SDL_CreateSystemCursor((SDL_SystemCursor)i);
    5.76 +        if (!cursors[1+i]) {
    5.77 +            SDL_Log("Error, couldn't create system cursor %d\n", i);
    5.78 +            quit(2);
    5.79 +        }
    5.80 +    }
    5.81 +    SDL_SetCursor(cursors[0]);
    5.82  
    5.83      /* Main render loop */
    5.84      done = 0;
    5.85 @@ -208,9 +247,13 @@
    5.86      }
    5.87  #endif
    5.88  
    5.89 -    SDL_FreeCursor(cursor);
    5.90 +    for (i = 0; i < SDL_arraysize(cursors); ++i) {
    5.91 +        SDL_FreeCursor(cursors[i]);
    5.92 +    }
    5.93      quit(0);
    5.94  
    5.95      /* keep the compiler happy ... */
    5.96      return(0);
    5.97  }
    5.98 +
    5.99 +/* vi: set ts=4 sw=4 expandtab: */