├── README.md ├── android └── template │ ├── AndroidManifest.xml │ ├── ant.properties │ ├── build.properties │ ├── build.xml │ ├── default.properties │ ├── jni │ ├── Android.mk │ ├── Application.mk │ ├── SDL │ │ └── Android.mk │ └── src │ │ ├── Android.mk │ │ └── Android_static.mk │ ├── proguard-project.txt │ ├── project.properties │ ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ ├── layout │ │ └── main.xml │ └── values │ │ └── strings.xml │ └── src │ └── org │ └── libsdl │ └── app │ └── SDLActivity.java ├── main.nim ├── nakefile.nim ├── res ├── Base.lproj │ └── LaunchScreen.nib ├── Info.plist └── PkgInfo └── src ├── application.nim └── nimx ├── button.nim ├── context.nim ├── logging.nim ├── sdl_window.nim ├── view.nim └── window.nim /README.md: -------------------------------------------------------------------------------- 1 | This repo is discontinued in favour of [nimx](https://github.com/yglukhov/nimx). No guarantees that it will work with latest versions of everything. 2 | 3 | # Cross-platform [SDL](http://libsdl.org) project in [Nim](http://nim-lang.org) 4 | 5 | This is a project template that builds and runs on different target platforms. 6 | 7 | ## Dependencies: 8 | - SDL. Get SDL2 sources, and set ```sdlRoot``` in ```nakefile.nim``` 9 | - XCode. Required to build SDL for mac, ios, ios-sim. Not needed for android. 10 | - Android SDK. Set ```androidSdk``` in ```nakefile.nim``` 11 | - Android NDK. Set ```androidNdk``` in ```nakefile.nim``` 12 | - Ant. Required to package and install on android. 13 | 14 | ## Setup 15 | - Change appropriate options in ```nakefile.nim```: ```appName```, ```bundleId```, ```javaPackageId```, ```sdlRoot```, ```nimIncludeDir```. 16 | - To run in ios-simulator make sure to set ```iOSSimulatorDeviceId```. 17 | 18 | ## Building 19 | ``` 20 | $ nake 21 | ``` 22 | 23 | List available tasks: 24 | ``` 25 | $ nake help 26 | ``` 27 | 28 | ## Feedback 29 | Please feel free to submit pull requests, bug reports, etc. 30 | 31 | -------------------------------------------------------------------------------- /android/template/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/template/ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked into Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /android/template/build.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked in Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /android/template/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 50 | 51 | 52 | 53 | 57 | 58 | 70 | 71 | 72 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /android/template/default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-12 12 | -------------------------------------------------------------------------------- /android/template/jni/Android.mk: -------------------------------------------------------------------------------- 1 | include $(call all-subdir-makefiles) 2 | -------------------------------------------------------------------------------- /android/template/jni/Application.mk: -------------------------------------------------------------------------------- 1 | 2 | # Uncomment this if you're using STL in your project 3 | # See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information 4 | # APP_STL := stlport_static 5 | 6 | APP_ABI := armeabi armeabi-v7a x86 7 | 8 | -------------------------------------------------------------------------------- /android/template/jni/SDL/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | ########################### 4 | # 5 | # SDL shared library 6 | # 7 | ########################### 8 | 9 | include $(CLEAR_VARS) 10 | 11 | LOCAL_MODULE := SDL2 12 | 13 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/include 14 | 15 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) 16 | 17 | LOCAL_SRC_FILES := \ 18 | $(subst $(LOCAL_PATH)/,, \ 19 | $(wildcard $(LOCAL_PATH)/src/*.c) \ 20 | $(wildcard $(LOCAL_PATH)/src/audio/*.c) \ 21 | $(wildcard $(LOCAL_PATH)/src/audio/android/*.c) \ 22 | $(wildcard $(LOCAL_PATH)/src/audio/dummy/*.c) \ 23 | $(LOCAL_PATH)/src/atomic/SDL_atomic.c \ 24 | $(LOCAL_PATH)/src/atomic/SDL_spinlock.c.arm \ 25 | $(wildcard $(LOCAL_PATH)/src/core/android/*.c) \ 26 | $(wildcard $(LOCAL_PATH)/src/cpuinfo/*.c) \ 27 | $(wildcard $(LOCAL_PATH)/src/dynapi/*.c) \ 28 | $(wildcard $(LOCAL_PATH)/src/events/*.c) \ 29 | $(wildcard $(LOCAL_PATH)/src/file/*.c) \ 30 | $(wildcard $(LOCAL_PATH)/src/haptic/*.c) \ 31 | $(wildcard $(LOCAL_PATH)/src/haptic/dummy/*.c) \ 32 | $(wildcard $(LOCAL_PATH)/src/joystick/*.c) \ 33 | $(wildcard $(LOCAL_PATH)/src/joystick/android/*.c) \ 34 | $(wildcard $(LOCAL_PATH)/src/loadso/dlopen/*.c) \ 35 | $(wildcard $(LOCAL_PATH)/src/power/*.c) \ 36 | $(wildcard $(LOCAL_PATH)/src/power/android/*.c) \ 37 | $(wildcard $(LOCAL_PATH)/src/filesystem/dummy/*.c) \ 38 | $(wildcard $(LOCAL_PATH)/src/render/*.c) \ 39 | $(wildcard $(LOCAL_PATH)/src/render/*/*.c) \ 40 | $(wildcard $(LOCAL_PATH)/src/stdlib/*.c) \ 41 | $(wildcard $(LOCAL_PATH)/src/thread/*.c) \ 42 | $(wildcard $(LOCAL_PATH)/src/thread/pthread/*.c) \ 43 | $(wildcard $(LOCAL_PATH)/src/timer/*.c) \ 44 | $(wildcard $(LOCAL_PATH)/src/timer/unix/*.c) \ 45 | $(wildcard $(LOCAL_PATH)/src/video/*.c) \ 46 | $(wildcard $(LOCAL_PATH)/src/video/android/*.c) \ 47 | $(wildcard $(LOCAL_PATH)/src/test/*.c)) 48 | 49 | LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES 50 | LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -llog -landroid 51 | 52 | include $(BUILD_SHARED_LIBRARY) 53 | 54 | ########################### 55 | # 56 | # SDL static library 57 | # 58 | ########################### 59 | 60 | LOCAL_MODULE := SDL2_static 61 | 62 | LOCAL_MODULE_FILENAME := libSDL2 63 | 64 | APP_SRC := $(LOCAL_PATH)/.. 65 | 66 | LOCAL_SRC_FILES += $(LOCAL_PATH)/src/main/android/SDL_android_main.c 67 | 68 | LOCAL_LDLIBS := 69 | LOCAL_EXPORT_LDLIBS := -Wl,--undefined=Java_org_libsdl_app_SDLActivity_nativeInit -ldl -lGLESv1_CM -lGLESv2 -llog -landroid 70 | 71 | include $(BUILD_STATIC_LIBRARY) 72 | -------------------------------------------------------------------------------- /android/template/jni/src/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_MODULE := main 6 | 7 | SDL_PATH := ../SDL 8 | 9 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include $(NIM_INCLUDE_DIR) 10 | 11 | # Add your application source files here... 12 | LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ 13 | $(patsubst $(LOCAL_PATH)/%, %, $(wildcard $(LOCAL_PATH)/*.cpp)) \ 14 | $(patsubst $(LOCAL_PATH)/%, %, $(wildcard $(LOCAL_PATH)/*.c)) 15 | 16 | 17 | LOCAL_SHARED_LIBRARIES := SDL2 18 | 19 | LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog 20 | 21 | include $(BUILD_SHARED_LIBRARY) 22 | -------------------------------------------------------------------------------- /android/template/jni/src/Android_static.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_MODULE := main 6 | 7 | LOCAL_SRC_FILES := $(patsubst $(LOCAL_PATH)/%, %, $(wildcard $(LOCAL_PATH)/src/*.cpp)) \ 8 | $(patsubst $(LOCAL_PATH)/%, %, $(wildcard $(LOCAL_PATH)/src/*.c)) 9 | 10 | LOCAL_STATIC_LIBRARIES := SDL2_static 11 | 12 | include $(BUILD_SHARED_LIBRARY) 13 | $(call import-module,SDL)LOCAL_PATH := $(call my-dir) 14 | -------------------------------------------------------------------------------- /android/template/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /android/template/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-20 15 | -------------------------------------------------------------------------------- /android/template/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yglukhov/nim-sdl-template/6ecfd693e89f54aa6681e7dc8a2796e7a44654d3/android/template/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/template/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yglukhov/nim-sdl-template/6ecfd693e89f54aa6681e7dc8a2796e7a44654d3/android/template/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/template/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yglukhov/nim-sdl-template/6ecfd693e89f54aa6681e7dc8a2796e7a44654d3/android/template/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/template/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yglukhov/nim-sdl-template/6ecfd693e89f54aa6681e7dc8a2796e7a44654d3/android/template/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/template/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/template/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(APP_NAME) 4 | 5 | -------------------------------------------------------------------------------- /android/template/src/org/libsdl/app/SDLActivity.java: -------------------------------------------------------------------------------- 1 | package org.libsdl.app; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | 9 | import android.app.*; 10 | import android.content.*; 11 | import android.view.*; 12 | import android.view.inputmethod.BaseInputConnection; 13 | import android.view.inputmethod.EditorInfo; 14 | import android.view.inputmethod.InputConnection; 15 | import android.view.inputmethod.InputMethodManager; 16 | import android.widget.AbsoluteLayout; 17 | import android.os.*; 18 | import android.util.Log; 19 | import android.graphics.*; 20 | import android.media.*; 21 | import android.hardware.*; 22 | 23 | 24 | /** 25 | SDL Activity 26 | */ 27 | public class SDLActivity extends Activity { 28 | private static final String TAG = "SDL"; 29 | 30 | // Keep track of the paused state 31 | public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; 32 | public static boolean mExitCalledFromJava; 33 | 34 | // Main components 35 | protected static SDLActivity mSingleton; 36 | protected static SDLSurface mSurface; 37 | protected static View mTextEdit; 38 | protected static ViewGroup mLayout; 39 | protected static SDLJoystickHandler mJoystickHandler; 40 | 41 | // This is what SDL runs in. It invokes SDL_main(), eventually 42 | protected static Thread mSDLThread; 43 | 44 | // Audio 45 | protected static AudioTrack mAudioTrack; 46 | 47 | // Load the .so 48 | static { 49 | System.loadLibrary("SDL2"); 50 | //System.loadLibrary("SDL2_image"); 51 | //System.loadLibrary("SDL2_mixer"); 52 | //System.loadLibrary("SDL2_net"); 53 | //System.loadLibrary("SDL2_ttf"); 54 | System.loadLibrary("main"); 55 | } 56 | 57 | 58 | public static void initialize() { 59 | // The static nature of the singleton and Android quirkyness force us to initialize everything here 60 | // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values 61 | mSingleton = null; 62 | mSurface = null; 63 | mTextEdit = null; 64 | mLayout = null; 65 | mJoystickHandler = null; 66 | mSDLThread = null; 67 | mAudioTrack = null; 68 | mExitCalledFromJava = false; 69 | mIsPaused = false; 70 | mIsSurfaceReady = false; 71 | mHasFocus = true; 72 | } 73 | 74 | // Setup 75 | @Override 76 | protected void onCreate(Bundle savedInstanceState) { 77 | Log.v("SDL", "onCreate():" + mSingleton); 78 | super.onCreate(savedInstanceState); 79 | 80 | SDLActivity.initialize(); 81 | // So we can call stuff from static callbacks 82 | mSingleton = this; 83 | 84 | // Set up the surface 85 | mSurface = new SDLSurface(getApplication()); 86 | 87 | if(Build.VERSION.SDK_INT >= 12) { 88 | mJoystickHandler = new SDLJoystickHandler_API12(); 89 | } 90 | else { 91 | mJoystickHandler = new SDLJoystickHandler(); 92 | } 93 | 94 | mLayout = new AbsoluteLayout(this); 95 | mLayout.addView(mSurface); 96 | 97 | setContentView(mLayout); 98 | } 99 | 100 | // Events 101 | @Override 102 | protected void onPause() { 103 | Log.v("SDL", "onPause()"); 104 | super.onPause(); 105 | SDLActivity.handlePause(); 106 | } 107 | 108 | @Override 109 | protected void onResume() { 110 | Log.v("SDL", "onResume()"); 111 | super.onResume(); 112 | SDLActivity.handleResume(); 113 | } 114 | 115 | 116 | @Override 117 | public void onWindowFocusChanged(boolean hasFocus) { 118 | super.onWindowFocusChanged(hasFocus); 119 | Log.v("SDL", "onWindowFocusChanged(): " + hasFocus); 120 | 121 | SDLActivity.mHasFocus = hasFocus; 122 | if (hasFocus) { 123 | SDLActivity.handleResume(); 124 | } 125 | } 126 | 127 | @Override 128 | public void onLowMemory() { 129 | Log.v("SDL", "onLowMemory()"); 130 | super.onLowMemory(); 131 | SDLActivity.nativeLowMemory(); 132 | } 133 | 134 | @Override 135 | protected void onDestroy() { 136 | Log.v("SDL", "onDestroy()"); 137 | // Send a quit message to the application 138 | SDLActivity.mExitCalledFromJava = true; 139 | SDLActivity.nativeQuit(); 140 | 141 | // Now wait for the SDL thread to quit 142 | if (SDLActivity.mSDLThread != null) { 143 | try { 144 | SDLActivity.mSDLThread.join(); 145 | } catch(Exception e) { 146 | Log.v("SDL", "Problem stopping thread: " + e); 147 | } 148 | SDLActivity.mSDLThread = null; 149 | 150 | //Log.v("SDL", "Finished waiting for SDL thread"); 151 | } 152 | 153 | super.onDestroy(); 154 | // Reset everything in case the user re opens the app 155 | SDLActivity.initialize(); 156 | } 157 | 158 | @Override 159 | public boolean dispatchKeyEvent(KeyEvent event) { 160 | int keyCode = event.getKeyCode(); 161 | // Ignore certain special keys so they're handled by Android 162 | if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || 163 | keyCode == KeyEvent.KEYCODE_VOLUME_UP || 164 | keyCode == KeyEvent.KEYCODE_CAMERA || 165 | keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ 166 | keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ 167 | ) { 168 | return false; 169 | } 170 | return super.dispatchKeyEvent(event); 171 | } 172 | 173 | /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed 174 | * is the first to be called, mIsSurfaceReady should still be set 175 | * to 'true' during the call to onPause (in a usual scenario). 176 | */ 177 | public static void handlePause() { 178 | if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { 179 | SDLActivity.mIsPaused = true; 180 | SDLActivity.nativePause(); 181 | mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); 182 | } 183 | } 184 | 185 | /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. 186 | * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume 187 | * every time we get one of those events, only if it comes after surfaceDestroyed 188 | */ 189 | public static void handleResume() { 190 | if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { 191 | SDLActivity.mIsPaused = false; 192 | SDLActivity.nativeResume(); 193 | mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); 194 | } 195 | } 196 | 197 | /* The native thread has finished */ 198 | public static void handleNativeExit() { 199 | SDLActivity.mSDLThread = null; 200 | mSingleton.finish(); 201 | } 202 | 203 | 204 | // Messages from the SDLMain thread 205 | static final int COMMAND_CHANGE_TITLE = 1; 206 | static final int COMMAND_UNUSED = 2; 207 | static final int COMMAND_TEXTEDIT_HIDE = 3; 208 | 209 | protected static final int COMMAND_USER = 0x8000; 210 | 211 | /** 212 | * This method is called by SDL if SDL did not handle a message itself. 213 | * This happens if a received message contains an unsupported command. 214 | * Method can be overwritten to handle Messages in a different class. 215 | * @param command the command of the message. 216 | * @param param the parameter of the message. May be null. 217 | * @return if the message was handled in overridden method. 218 | */ 219 | protected boolean onUnhandledMessage(int command, Object param) { 220 | return false; 221 | } 222 | 223 | /** 224 | * A Handler class for Messages from native SDL applications. 225 | * It uses current Activities as target (e.g. for the title). 226 | * static to prevent implicit references to enclosing object. 227 | */ 228 | protected static class SDLCommandHandler extends Handler { 229 | @Override 230 | public void handleMessage(Message msg) { 231 | Context context = getContext(); 232 | if (context == null) { 233 | Log.e(TAG, "error handling message, getContext() returned null"); 234 | return; 235 | } 236 | switch (msg.arg1) { 237 | case COMMAND_CHANGE_TITLE: 238 | if (context instanceof Activity) { 239 | ((Activity) context).setTitle((String)msg.obj); 240 | } else { 241 | Log.e(TAG, "error handling message, getContext() returned no Activity"); 242 | } 243 | break; 244 | case COMMAND_TEXTEDIT_HIDE: 245 | if (mTextEdit != null) { 246 | mTextEdit.setVisibility(View.GONE); 247 | 248 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 249 | imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); 250 | } 251 | break; 252 | 253 | default: 254 | if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { 255 | Log.e(TAG, "error handling message, command is " + msg.arg1); 256 | } 257 | } 258 | } 259 | } 260 | 261 | // Handler for the messages 262 | Handler commandHandler = new SDLCommandHandler(); 263 | 264 | // Send a message from the SDLMain thread 265 | boolean sendCommand(int command, Object data) { 266 | Message msg = commandHandler.obtainMessage(); 267 | msg.arg1 = command; 268 | msg.obj = data; 269 | return commandHandler.sendMessage(msg); 270 | } 271 | 272 | // C functions we call 273 | public static native void nativeInit(); 274 | public static native void nativeLowMemory(); 275 | public static native void nativeQuit(); 276 | public static native void nativePause(); 277 | public static native void nativeResume(); 278 | public static native void onNativeResize(int x, int y, int format); 279 | public static native int onNativePadDown(int device_id, int keycode); 280 | public static native int onNativePadUp(int device_id, int keycode); 281 | public static native void onNativeJoy(int device_id, int axis, 282 | float value); 283 | public static native void onNativeHat(int device_id, int hat_id, 284 | int x, int y); 285 | public static native void onNativeKeyDown(int keycode); 286 | public static native void onNativeKeyUp(int keycode); 287 | public static native void onNativeKeyboardFocusLost(); 288 | public static native void onNativeTouch(int touchDevId, int pointerFingerId, 289 | int action, float x, 290 | float y, float p); 291 | public static native void onNativeAccel(float x, float y, float z); 292 | public static native void onNativeSurfaceChanged(); 293 | public static native void onNativeSurfaceDestroyed(); 294 | public static native void nativeFlipBuffers(); 295 | public static native int nativeAddJoystick(int device_id, String name, 296 | int is_accelerometer, int nbuttons, 297 | int naxes, int nhats, int nballs); 298 | public static native int nativeRemoveJoystick(int device_id); 299 | 300 | public static void flipBuffers() { 301 | SDLActivity.nativeFlipBuffers(); 302 | } 303 | 304 | public static boolean setActivityTitle(String title) { 305 | // Called from SDLMain() thread and can't directly affect the view 306 | return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); 307 | } 308 | 309 | public static boolean sendMessage(int command, int param) { 310 | return mSingleton.sendCommand(command, Integer.valueOf(param)); 311 | } 312 | 313 | public static Context getContext() { 314 | return mSingleton; 315 | } 316 | 317 | /** 318 | * @return result of getSystemService(name) but executed on UI thread. 319 | */ 320 | public Object getSystemServiceFromUiThread(final String name) { 321 | final Object lock = new Object(); 322 | final Object[] results = new Object[2]; // array for writable variables 323 | synchronized (lock) { 324 | runOnUiThread(new Runnable() { 325 | @Override 326 | public void run() { 327 | synchronized (lock) { 328 | results[0] = getSystemService(name); 329 | results[1] = Boolean.TRUE; 330 | lock.notify(); 331 | } 332 | } 333 | }); 334 | if (results[1] == null) { 335 | try { 336 | lock.wait(); 337 | } catch (InterruptedException ex) { 338 | ex.printStackTrace(); 339 | } 340 | } 341 | } 342 | return results[0]; 343 | } 344 | 345 | static class ShowTextInputTask implements Runnable { 346 | /* 347 | * This is used to regulate the pan&scan method to have some offset from 348 | * the bottom edge of the input region and the top edge of an input 349 | * method (soft keyboard) 350 | */ 351 | static final int HEIGHT_PADDING = 15; 352 | 353 | public int x, y, w, h; 354 | 355 | public ShowTextInputTask(int x, int y, int w, int h) { 356 | this.x = x; 357 | this.y = y; 358 | this.w = w; 359 | this.h = h; 360 | } 361 | 362 | @Override 363 | public void run() { 364 | AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( 365 | w, h + HEIGHT_PADDING, x, y); 366 | 367 | if (mTextEdit == null) { 368 | mTextEdit = new DummyEdit(getContext()); 369 | 370 | mLayout.addView(mTextEdit, params); 371 | } else { 372 | mTextEdit.setLayoutParams(params); 373 | } 374 | 375 | mTextEdit.setVisibility(View.VISIBLE); 376 | mTextEdit.requestFocus(); 377 | 378 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 379 | imm.showSoftInput(mTextEdit, 0); 380 | } 381 | } 382 | 383 | public static boolean showTextInput(int x, int y, int w, int h) { 384 | // Transfer the task to the main thread as a Runnable 385 | return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); 386 | } 387 | 388 | public static Surface getNativeSurface() { 389 | return SDLActivity.mSurface.getNativeSurface(); 390 | } 391 | 392 | // Audio 393 | public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { 394 | int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; 395 | int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; 396 | int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); 397 | 398 | Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); 399 | 400 | // Let the user pick a larger buffer if they really want -- but ye 401 | // gods they probably shouldn't, the minimums are horrifyingly high 402 | // latency already 403 | desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); 404 | 405 | if (mAudioTrack == null) { 406 | mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, 407 | channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); 408 | 409 | // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid 410 | // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java 411 | // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() 412 | 413 | if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { 414 | Log.e("SDL", "Failed during initialization of Audio Track"); 415 | mAudioTrack = null; 416 | return -1; 417 | } 418 | 419 | mAudioTrack.play(); 420 | } 421 | 422 | Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); 423 | 424 | return 0; 425 | } 426 | 427 | public static void audioWriteShortBuffer(short[] buffer) { 428 | for (int i = 0; i < buffer.length; ) { 429 | int result = mAudioTrack.write(buffer, i, buffer.length - i); 430 | if (result > 0) { 431 | i += result; 432 | } else if (result == 0) { 433 | try { 434 | Thread.sleep(1); 435 | } catch(InterruptedException e) { 436 | // Nom nom 437 | } 438 | } else { 439 | Log.w("SDL", "SDL audio: error return from write(short)"); 440 | return; 441 | } 442 | } 443 | } 444 | 445 | public static void audioWriteByteBuffer(byte[] buffer) { 446 | for (int i = 0; i < buffer.length; ) { 447 | int result = mAudioTrack.write(buffer, i, buffer.length - i); 448 | if (result > 0) { 449 | i += result; 450 | } else if (result == 0) { 451 | try { 452 | Thread.sleep(1); 453 | } catch(InterruptedException e) { 454 | // Nom nom 455 | } 456 | } else { 457 | Log.w("SDL", "SDL audio: error return from write(byte)"); 458 | return; 459 | } 460 | } 461 | } 462 | 463 | public static void audioQuit() { 464 | if (mAudioTrack != null) { 465 | mAudioTrack.stop(); 466 | mAudioTrack = null; 467 | } 468 | } 469 | 470 | // Input 471 | 472 | /** 473 | * @return an array which may be empty but is never null. 474 | */ 475 | public static int[] inputGetInputDeviceIds(int sources) { 476 | int[] ids = InputDevice.getDeviceIds(); 477 | int[] filtered = new int[ids.length]; 478 | int used = 0; 479 | for (int i = 0; i < ids.length; ++i) { 480 | InputDevice device = InputDevice.getDevice(ids[i]); 481 | if ((device != null) && ((device.getSources() & sources) != 0)) { 482 | filtered[used++] = device.getId(); 483 | } 484 | } 485 | return Arrays.copyOf(filtered, used); 486 | } 487 | 488 | // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance 489 | public static boolean handleJoystickMotionEvent(MotionEvent event) { 490 | return mJoystickHandler.handleMotionEvent(event); 491 | } 492 | 493 | public static void pollInputDevices() { 494 | if (SDLActivity.mSDLThread != null) { 495 | mJoystickHandler.pollInputDevices(); 496 | } 497 | } 498 | 499 | } 500 | 501 | /** 502 | Simple nativeInit() runnable 503 | */ 504 | class SDLMain implements Runnable { 505 | @Override 506 | public void run() { 507 | // Runs SDL_main() 508 | SDLActivity.nativeInit(); 509 | 510 | //Log.v("SDL", "SDL thread terminated"); 511 | } 512 | } 513 | 514 | 515 | /** 516 | SDLSurface. This is what we draw on, so we need to know when it's created 517 | in order to do anything useful. 518 | 519 | Because of this, that's where we set up the SDL thread 520 | */ 521 | class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, 522 | View.OnKeyListener, View.OnTouchListener, SensorEventListener { 523 | 524 | // Sensors 525 | protected static SensorManager mSensorManager; 526 | protected static Display mDisplay; 527 | 528 | // Keep track of the surface size to normalize touch events 529 | protected static float mWidth, mHeight; 530 | 531 | // Startup 532 | public SDLSurface(Context context) { 533 | super(context); 534 | getHolder().addCallback(this); 535 | 536 | setFocusable(true); 537 | setFocusableInTouchMode(true); 538 | requestFocus(); 539 | setOnKeyListener(this); 540 | setOnTouchListener(this); 541 | 542 | mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 543 | mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); 544 | 545 | if(Build.VERSION.SDK_INT >= 12) { 546 | setOnGenericMotionListener(new SDLGenericMotionListener_API12()); 547 | } 548 | 549 | // Some arbitrary defaults to avoid a potential division by zero 550 | mWidth = 1.0f; 551 | mHeight = 1.0f; 552 | } 553 | 554 | public Surface getNativeSurface() { 555 | return getHolder().getSurface(); 556 | } 557 | 558 | // Called when we have a valid drawing surface 559 | @Override 560 | public void surfaceCreated(SurfaceHolder holder) { 561 | Log.v("SDL", "surfaceCreated()"); 562 | holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); 563 | } 564 | 565 | // Called when we lose the surface 566 | @Override 567 | public void surfaceDestroyed(SurfaceHolder holder) { 568 | Log.v("SDL", "surfaceDestroyed()"); 569 | // Call this *before* setting mIsSurfaceReady to 'false' 570 | SDLActivity.handlePause(); 571 | SDLActivity.mIsSurfaceReady = false; 572 | SDLActivity.onNativeSurfaceDestroyed(); 573 | } 574 | 575 | // Called when the surface is resized 576 | @Override 577 | public void surfaceChanged(SurfaceHolder holder, 578 | int format, int width, int height) { 579 | Log.v("SDL", "surfaceChanged()"); 580 | 581 | int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default 582 | switch (format) { 583 | case PixelFormat.A_8: 584 | Log.v("SDL", "pixel format A_8"); 585 | break; 586 | case PixelFormat.LA_88: 587 | Log.v("SDL", "pixel format LA_88"); 588 | break; 589 | case PixelFormat.L_8: 590 | Log.v("SDL", "pixel format L_8"); 591 | break; 592 | case PixelFormat.RGBA_4444: 593 | Log.v("SDL", "pixel format RGBA_4444"); 594 | sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444 595 | break; 596 | case PixelFormat.RGBA_5551: 597 | Log.v("SDL", "pixel format RGBA_5551"); 598 | sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551 599 | break; 600 | case PixelFormat.RGBA_8888: 601 | Log.v("SDL", "pixel format RGBA_8888"); 602 | sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888 603 | break; 604 | case PixelFormat.RGBX_8888: 605 | Log.v("SDL", "pixel format RGBX_8888"); 606 | sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888 607 | break; 608 | case PixelFormat.RGB_332: 609 | Log.v("SDL", "pixel format RGB_332"); 610 | sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332 611 | break; 612 | case PixelFormat.RGB_565: 613 | Log.v("SDL", "pixel format RGB_565"); 614 | sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 615 | break; 616 | case PixelFormat.RGB_888: 617 | Log.v("SDL", "pixel format RGB_888"); 618 | // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead? 619 | sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888 620 | break; 621 | default: 622 | Log.v("SDL", "pixel format unknown " + format); 623 | break; 624 | } 625 | 626 | mWidth = width; 627 | mHeight = height; 628 | SDLActivity.onNativeResize(width, height, sdlFormat); 629 | Log.v("SDL", "Window size:" + width + "x"+height); 630 | 631 | // Set mIsSurfaceReady to 'true' *before* making a call to handleResume 632 | SDLActivity.mIsSurfaceReady = true; 633 | SDLActivity.onNativeSurfaceChanged(); 634 | 635 | 636 | if (SDLActivity.mSDLThread == null) { 637 | // This is the entry point to the C app. 638 | // Start up the C app thread and enable sensor input for the first time 639 | 640 | SDLActivity.mSDLThread = new Thread(new SDLMain(), "SDLThread"); 641 | enableSensor(Sensor.TYPE_ACCELEROMETER, true); 642 | SDLActivity.mSDLThread.start(); 643 | 644 | // Set up a listener thread to catch when the native thread ends 645 | new Thread(new Runnable(){ 646 | @Override 647 | public void run(){ 648 | try { 649 | SDLActivity.mSDLThread.join(); 650 | } 651 | catch(Exception e){} 652 | finally{ 653 | // Native thread has finished 654 | if (! SDLActivity.mExitCalledFromJava) { 655 | SDLActivity.handleNativeExit(); 656 | } 657 | } 658 | } 659 | }).start(); 660 | } 661 | } 662 | 663 | // unused 664 | @Override 665 | public void onDraw(Canvas canvas) {} 666 | 667 | 668 | // Key events 669 | @Override 670 | public boolean onKey(View v, int keyCode, KeyEvent event) { 671 | // Dispatch the different events depending on where they come from 672 | // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD 673 | // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD 674 | 675 | if ( (event.getSource() & 0x00000401) != 0 || /* API 12: SOURCE_GAMEPAD */ 676 | (event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) { 677 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 678 | if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) { 679 | return true; 680 | } 681 | } else if (event.getAction() == KeyEvent.ACTION_UP) { 682 | if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) { 683 | return true; 684 | } 685 | } 686 | } 687 | 688 | if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) { 689 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 690 | //Log.v("SDL", "key down: " + keyCode); 691 | SDLActivity.onNativeKeyDown(keyCode); 692 | return true; 693 | } 694 | else if (event.getAction() == KeyEvent.ACTION_UP) { 695 | //Log.v("SDL", "key up: " + keyCode); 696 | SDLActivity.onNativeKeyUp(keyCode); 697 | return true; 698 | } 699 | } 700 | 701 | return false; 702 | } 703 | 704 | // Touch events 705 | @Override 706 | public boolean onTouch(View v, MotionEvent event) { 707 | /* Ref: http://developer.android.com/training/gestures/multi.html */ 708 | final int touchDevId = event.getDeviceId(); 709 | final int pointerCount = event.getPointerCount(); 710 | int action = event.getActionMasked(); 711 | int pointerFingerId; 712 | int i = -1; 713 | float x,y,p; 714 | 715 | switch(action) { 716 | case MotionEvent.ACTION_MOVE: 717 | for (i = 0; i < pointerCount; i++) { 718 | pointerFingerId = event.getPointerId(i); 719 | x = event.getX(i) / mWidth; 720 | y = event.getY(i) / mHeight; 721 | p = event.getPressure(i); 722 | SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); 723 | } 724 | break; 725 | 726 | case MotionEvent.ACTION_UP: 727 | case MotionEvent.ACTION_DOWN: 728 | // Primary pointer up/down, the index is always zero 729 | i = 0; 730 | case MotionEvent.ACTION_POINTER_UP: 731 | case MotionEvent.ACTION_POINTER_DOWN: 732 | // Non primary pointer up/down 733 | if (i == -1) { 734 | i = event.getActionIndex(); 735 | } 736 | 737 | pointerFingerId = event.getPointerId(i); 738 | x = event.getX(i) / mWidth; 739 | y = event.getY(i) / mHeight; 740 | p = event.getPressure(i); 741 | SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); 742 | break; 743 | 744 | default: 745 | break; 746 | } 747 | 748 | return true; 749 | } 750 | 751 | // Sensor events 752 | public void enableSensor(int sensortype, boolean enabled) { 753 | // TODO: This uses getDefaultSensor - what if we have >1 accels? 754 | if (enabled) { 755 | mSensorManager.registerListener(this, 756 | mSensorManager.getDefaultSensor(sensortype), 757 | SensorManager.SENSOR_DELAY_GAME, null); 758 | } else { 759 | mSensorManager.unregisterListener(this, 760 | mSensorManager.getDefaultSensor(sensortype)); 761 | } 762 | } 763 | 764 | @Override 765 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 766 | // TODO 767 | } 768 | 769 | @Override 770 | public void onSensorChanged(SensorEvent event) { 771 | if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { 772 | float x, y; 773 | switch (mDisplay.getRotation()) { 774 | case Surface.ROTATION_90: 775 | x = -event.values[1]; 776 | y = event.values[0]; 777 | break; 778 | case Surface.ROTATION_270: 779 | x = event.values[1]; 780 | y = -event.values[0]; 781 | break; 782 | case Surface.ROTATION_180: 783 | x = -event.values[1]; 784 | y = -event.values[0]; 785 | break; 786 | default: 787 | x = event.values[0]; 788 | y = event.values[1]; 789 | break; 790 | } 791 | SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, 792 | y / SensorManager.GRAVITY_EARTH, 793 | event.values[2] / SensorManager.GRAVITY_EARTH - 1); 794 | } 795 | } 796 | } 797 | 798 | /* This is a fake invisible editor view that receives the input and defines the 799 | * pan&scan region 800 | */ 801 | class DummyEdit extends View implements View.OnKeyListener { 802 | InputConnection ic; 803 | 804 | public DummyEdit(Context context) { 805 | super(context); 806 | setFocusableInTouchMode(true); 807 | setFocusable(true); 808 | setOnKeyListener(this); 809 | } 810 | 811 | @Override 812 | public boolean onCheckIsTextEditor() { 813 | return true; 814 | } 815 | 816 | @Override 817 | public boolean onKey(View v, int keyCode, KeyEvent event) { 818 | 819 | // This handles the hardware keyboard input 820 | if (event.isPrintingKey()) { 821 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 822 | ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); 823 | } 824 | return true; 825 | } 826 | 827 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 828 | SDLActivity.onNativeKeyDown(keyCode); 829 | return true; 830 | } else if (event.getAction() == KeyEvent.ACTION_UP) { 831 | SDLActivity.onNativeKeyUp(keyCode); 832 | return true; 833 | } 834 | 835 | return false; 836 | } 837 | 838 | // 839 | @Override 840 | public boolean onKeyPreIme (int keyCode, KeyEvent event) { 841 | // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event 842 | // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 843 | // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not 844 | // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear 845 | // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android 846 | // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :) 847 | if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { 848 | if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) { 849 | SDLActivity.onNativeKeyboardFocusLost(); 850 | } 851 | } 852 | return super.onKeyPreIme(keyCode, event); 853 | } 854 | 855 | @Override 856 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 857 | ic = new SDLInputConnection(this, true); 858 | 859 | outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI 860 | | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */; 861 | 862 | return ic; 863 | } 864 | } 865 | 866 | class SDLInputConnection extends BaseInputConnection { 867 | 868 | public SDLInputConnection(View targetView, boolean fullEditor) { 869 | super(targetView, fullEditor); 870 | 871 | } 872 | 873 | @Override 874 | public boolean sendKeyEvent(KeyEvent event) { 875 | 876 | /* 877 | * This handles the keycodes from soft keyboard (and IME-translated 878 | * input from hardkeyboard) 879 | */ 880 | int keyCode = event.getKeyCode(); 881 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 882 | if (event.isPrintingKey()) { 883 | commitText(String.valueOf((char) event.getUnicodeChar()), 1); 884 | } 885 | SDLActivity.onNativeKeyDown(keyCode); 886 | return true; 887 | } else if (event.getAction() == KeyEvent.ACTION_UP) { 888 | 889 | SDLActivity.onNativeKeyUp(keyCode); 890 | return true; 891 | } 892 | return super.sendKeyEvent(event); 893 | } 894 | 895 | @Override 896 | public boolean commitText(CharSequence text, int newCursorPosition) { 897 | 898 | nativeCommitText(text.toString(), newCursorPosition); 899 | 900 | return super.commitText(text, newCursorPosition); 901 | } 902 | 903 | @Override 904 | public boolean setComposingText(CharSequence text, int newCursorPosition) { 905 | 906 | nativeSetComposingText(text.toString(), newCursorPosition); 907 | 908 | return super.setComposingText(text, newCursorPosition); 909 | } 910 | 911 | public native void nativeCommitText(String text, int newCursorPosition); 912 | 913 | public native void nativeSetComposingText(String text, int newCursorPosition); 914 | 915 | @Override 916 | public boolean deleteSurroundingText(int beforeLength, int afterLength) { 917 | // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection 918 | if (beforeLength == 1 && afterLength == 0) { 919 | // backspace 920 | return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) 921 | && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); 922 | } 923 | 924 | return super.deleteSurroundingText(beforeLength, afterLength); 925 | } 926 | } 927 | 928 | /* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ 929 | class SDLJoystickHandler { 930 | 931 | public boolean handleMotionEvent(MotionEvent event) { 932 | return false; 933 | } 934 | 935 | public void pollInputDevices() { 936 | } 937 | } 938 | 939 | /* Actual joystick functionality available for API >= 12 devices */ 940 | class SDLJoystickHandler_API12 extends SDLJoystickHandler { 941 | 942 | class SDLJoystick { 943 | public int device_id; 944 | public String name; 945 | public ArrayList axes; 946 | public ArrayList hats; 947 | } 948 | class RangeComparator implements Comparator 949 | { 950 | @Override 951 | public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { 952 | return arg0.getAxis() - arg1.getAxis(); 953 | } 954 | } 955 | 956 | private ArrayList mJoysticks; 957 | 958 | public SDLJoystickHandler_API12() { 959 | 960 | mJoysticks = new ArrayList(); 961 | } 962 | 963 | @Override 964 | public void pollInputDevices() { 965 | int[] deviceIds = InputDevice.getDeviceIds(); 966 | // It helps processing the device ids in reverse order 967 | // For example, in the case of the XBox 360 wireless dongle, 968 | // so the first controller seen by SDL matches what the receiver 969 | // considers to be the first controller 970 | 971 | for(int i=deviceIds.length-1; i>-1; i--) { 972 | SDLJoystick joystick = getJoystick(deviceIds[i]); 973 | if (joystick == null) { 974 | joystick = new SDLJoystick(); 975 | InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); 976 | if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { 977 | joystick.device_id = deviceIds[i]; 978 | joystick.name = joystickDevice.getName(); 979 | joystick.axes = new ArrayList(); 980 | joystick.hats = new ArrayList(); 981 | 982 | List ranges = joystickDevice.getMotionRanges(); 983 | Collections.sort(ranges, new RangeComparator()); 984 | for (InputDevice.MotionRange range : ranges ) { 985 | if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) { 986 | if (range.getAxis() == MotionEvent.AXIS_HAT_X || 987 | range.getAxis() == MotionEvent.AXIS_HAT_Y) { 988 | joystick.hats.add(range); 989 | } 990 | else { 991 | joystick.axes.add(range); 992 | } 993 | } 994 | } 995 | 996 | mJoysticks.add(joystick); 997 | SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1, 998 | joystick.axes.size(), joystick.hats.size()/2, 0); 999 | } 1000 | } 1001 | } 1002 | 1003 | /* Check removed devices */ 1004 | ArrayList removedDevices = new ArrayList(); 1005 | for(int i=0; i < mJoysticks.size(); i++) { 1006 | int device_id = mJoysticks.get(i).device_id; 1007 | int j; 1008 | for (j=0; j < deviceIds.length; j++) { 1009 | if (device_id == deviceIds[j]) break; 1010 | } 1011 | if (j == deviceIds.length) { 1012 | removedDevices.add(device_id); 1013 | } 1014 | } 1015 | 1016 | for(int i=0; i < removedDevices.size(); i++) { 1017 | int device_id = removedDevices.get(i); 1018 | SDLActivity.nativeRemoveJoystick(device_id); 1019 | for (int j=0; j < mJoysticks.size(); j++) { 1020 | if (mJoysticks.get(j).device_id == device_id) { 1021 | mJoysticks.remove(j); 1022 | break; 1023 | } 1024 | } 1025 | } 1026 | } 1027 | 1028 | protected SDLJoystick getJoystick(int device_id) { 1029 | for(int i=0; i < mJoysticks.size(); i++) { 1030 | if (mJoysticks.get(i).device_id == device_id) { 1031 | return mJoysticks.get(i); 1032 | } 1033 | } 1034 | return null; 1035 | } 1036 | 1037 | @Override 1038 | public boolean handleMotionEvent(MotionEvent event) { 1039 | if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { 1040 | int actionPointerIndex = event.getActionIndex(); 1041 | int action = event.getActionMasked(); 1042 | switch(action) { 1043 | case MotionEvent.ACTION_MOVE: 1044 | SDLJoystick joystick = getJoystick(event.getDeviceId()); 1045 | if ( joystick != null ) { 1046 | for (int i = 0; i < joystick.axes.size(); i++) { 1047 | InputDevice.MotionRange range = joystick.axes.get(i); 1048 | /* Normalize the value to -1...1 */ 1049 | float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; 1050 | SDLActivity.onNativeJoy(joystick.device_id, i, value ); 1051 | } 1052 | for (int i = 0; i < joystick.hats.size(); i+=2) { 1053 | int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); 1054 | int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); 1055 | SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY ); 1056 | } 1057 | } 1058 | break; 1059 | default: 1060 | break; 1061 | } 1062 | } 1063 | return true; 1064 | } 1065 | } 1066 | 1067 | class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { 1068 | // Generic Motion (mouse hover, joystick...) events go here 1069 | // We only have joysticks yet 1070 | @Override 1071 | public boolean onGenericMotion(View v, MotionEvent event) { 1072 | return SDLActivity.handleJoystickMotionEvent(event); 1073 | } 1074 | } 1075 | -------------------------------------------------------------------------------- /main.nim: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/nim c -r 2 | 3 | import src.application 4 | import macros 5 | 6 | {.emit: """ 7 | #include 8 | 9 | extern int cmdCount; 10 | extern char** cmdLine; 11 | extern char** gEnv; 12 | 13 | N_CDECL(void, NimMain)(void); 14 | 15 | int main(int argc, char** args) { 16 | cmdLine = args; 17 | cmdCount = argc; 18 | gEnv = NULL; 19 | NimMain(); 20 | return nim_program_result; 21 | } 22 | 23 | """.} 24 | 25 | 26 | macro passToCAndL(s: string): stmt = 27 | result = newNimNode(nnkStmtList) 28 | result.add parseStmt("{.passL: \"" & s.strVal & "\".}\n") 29 | result.add parseStmt("{.passC: \"" & s.strVal & "\".}\n") 30 | 31 | macro useFrameworks(n: varargs[string]): stmt = 32 | result = newNimNode(nnkStmtList, n) 33 | for i in 0..n.len-1: 34 | result.add parseStmt("passToCAndL(\"-framework " & n[i].strVal & "\")") 35 | 36 | when defined(ios): 37 | useFrameworks("OpenGLES", "UIKit") 38 | when not defined(simulator): 39 | {.passC:"-arch armv7".} 40 | {.passL:"-arch armv7".} 41 | elif defined(macosx): 42 | useFrameworks("OpenGL", "AppKit", "AudioUnit", "ForceFeedback", "IOKit", "Carbon", "CoreServices", "ApplicationServices") 43 | 44 | when defined(macosx) or defined(ios): 45 | useFrameworks("AudioToolbox", "CoreAudio", "CoreGraphics", "QuartzCore") 46 | 47 | 48 | -------------------------------------------------------------------------------- /nakefile.nim: -------------------------------------------------------------------------------- 1 | import nake 2 | import tables 3 | 4 | let appName = "MyGame" 5 | let bundleId = "com.mycompany.MyGame" 6 | let javaPackageId = "com.mycompany.MyGame" 7 | 8 | let androidSdk = "~/android-sdks" 9 | let androidNdk = "~/android-ndk-r8e" 10 | let sdlRoot = "~/Projects/SDL" 11 | 12 | # This should point to the Nim include dir, where nimbase.h resides. 13 | # Needed for android only 14 | let nimIncludeDir = "~/Projects/Nimrod/lib" 15 | 16 | let macOSSDKVersion = "10.10" 17 | let macOSMinVersion = "10.6" 18 | 19 | let iOSSDKVersion = "8.1" 20 | let iOSMinVersion = iOSSDKVersion 21 | 22 | # Simulator device identifier should be set to run the simulator. 23 | # Available simulators can be listed with the command: 24 | # $ xcrun simctl list 25 | let iOSSimulatorDeviceId = "F5D507BE-429C-4A14-861A-73A2335CAE52" 26 | 27 | let bundleName = appName & ".app" 28 | 29 | let parallelBuild = "--parallelBuild:1" 30 | let nimVerbose = "--verbosity:0" 31 | 32 | let xCodeApp = "/Applications/Xcode.app" 33 | 34 | let macOSSDK = xCodeApp/"Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX" & macOSSDKVersion & ".sdk" 35 | let iOSSDK = xCodeApp/"Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS" & iOSSDKVersion & ".sdk" 36 | let iOSSimulatorSDK = xCodeApp/"Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator" & iOSSDKVersion & ".sdk" 37 | 38 | proc infoPlistSetValueForKey(path, value, key: string) = 39 | direShell "defaults", "write", path, key, value 40 | 41 | proc absPath(path: string): string = 42 | if path.isAbsolute(): path else: getCurrentDir() / path 43 | 44 | proc makeBundle() = 45 | removeDir bundleName 46 | createDir bundleName 47 | for t, p in walkDir("res"): 48 | if t == pcDir: 49 | copyDir(p, bundleName / extractFilename(p)) 50 | else: 51 | copyFile(p, bundleName / extractFilename(p)) 52 | moveFile "main", bundleName / "main" 53 | let infoPlistPath = absPath(bundleName / "Info") 54 | infoPlistSetValueForKey(infoPlistPath, appName, "CFBundleName") 55 | infoPlistSetValueForKey(infoPlistPath, bundleId, "CFBundleIdentifier") 56 | 57 | proc symLink(source, destination: string) = 58 | direShell "ln", "-sf", source, destination 59 | 60 | proc createSDLIncludeLink(dir: string) = 61 | createDir dir 62 | symLink(sdlRoot/"include", dir/"SDL2") 63 | 64 | proc runAppInSimulator() = 65 | var waitForDebugger = "--wait-for-debugger" 66 | waitForDebugger = "" 67 | direShell "open", "-a", "iOS\\ Simulator" 68 | shell "xcrun", "simctl", "uninstall", iOSSimulatorDeviceId, bundleId 69 | direShell "xcrun", "simctl", "install", iOSSimulatorDeviceId, bundleName 70 | direShell "xcrun", "simctl", "launch", waitForDebugger, iOSSimulatorDeviceId, bundleId 71 | 72 | proc replaceVarsInFile(file: string, vars: Table[string, string]) = 73 | var content = readFile(file) 74 | for k, v in vars: 75 | content = content.replace("$(" & k & ")", v) 76 | writeFile(file, content) 77 | 78 | proc buildSDLForMacOS(): string = 79 | let xcodeProjDir = expandTilde(sdlRoot)/"Xcode/SDL" 80 | let libDir = xcodeProjDir/"build/Release" 81 | if not fileExists libDir/"libSDL2.a": 82 | direShell "xcodebuild", "-project", xcodeProjDir/"SDL.xcodeproj", "-target", "Static\\ Library", "-configuration", "Release", "-sdk", "macosx"&macOSSDKVersion, "SYMROOT=build" 83 | libDir 84 | 85 | 86 | proc buildSDLForIOS(forSimulator: bool = false): string = 87 | let entity = if forSimulator: "iphonesimulator" else: "iphoneos" 88 | let xcodeProjDir = expandTilde(sdlRoot)/"Xcode-iOS/SDL" 89 | let libDir = xcodeProjDir/"build/Release-" & entity 90 | if not fileExists libDir/"libSDL2.a": 91 | direShell "xcodebuild", "-project", xcodeProjDir/"SDL.xcodeproj", "-configuration", "Release", "-sdk", entity&iOSSDKVersion, "SYMROOT=build" 92 | libDir 93 | 94 | proc makeAndroidBuildDir(): string = 95 | let buildDir = "android"/javaPackageId 96 | if not dirExists buildDir: 97 | copyDir "android/template", buildDir 98 | symLink(sdlRoot/"src", buildDir/"jni/SDL/src") 99 | symLink(sdlRoot/"include", buildDir/"jni/SDL/include") 100 | createSDLIncludeLink(buildDir/"jni/src") 101 | 102 | let mainActivityPath = javaPackageId.replace(".", "/") 103 | createDir(buildDir/"src"/mainActivityPath) 104 | let mainActivityJava = """ 105 | package """ & javaPackageId & """; 106 | import org.libsdl.app.SDLActivity; 107 | public class MainActivity extends SDLActivity {} 108 | """ 109 | writeFile(buildDir/"src"/mainActivityPath/"MainActivity.java", mainActivityJava) 110 | 111 | let vars = { 112 | "PACKAGE_ID" : javaPackageId, 113 | "APP_NAME" : appName 114 | }.toTable() 115 | 116 | replaceVarsInFile buildDir/"AndroidManifest.xml", vars 117 | replaceVarsInFile buildDir/"res/values/strings.xml", vars 118 | buildDir 119 | 120 | proc runNim(arguments: varargs[string]) = 121 | var args = @[nimExe, "c", "--noMain", parallelBuild, "--stackTrace:off", "--lineTrace:off", 122 | nimVerbose, "-d:noAutoGLerrorCheck"] 123 | args.add arguments 124 | args.add "main" 125 | direShell args 126 | 127 | task defaultTask, "Build and run": 128 | createSDLIncludeLink "nimcache" 129 | runNim "--passC:-Inimcache", "--passC:-isysroot", "--passC:" & macOSSDK, "--passL:-isysroot", "--passL:" & macOSSDK, 130 | "--passC:-mmacosx-version-min=" & macOSMinVersion, "--passL:-mmacosx-version-min=" & macOSMinVersion, 131 | "--passL:-fobjc-link-runtime", "-d:SDL_Static", "--passL:-L"&buildSDLForMacOS(), "--passL:-lSDL2", 132 | "--run" 133 | 134 | task "ios-sim", "Build and run in iOS simulator": 135 | createSDLIncludeLink "nimcache" 136 | runNim "--passC:-Inimcache", "--cpu:amd64", "--os:macosx", "-d:ios", "-d:iPhone", "-d:simulator", "-d:SDL_Static", 137 | "--passC:-isysroot", "--passC:" & iOSSimulatorSDK, "--passL:-isysroot", "--passL:" & iOSSimulatorSDK, 138 | "--passL:-L" & buildSDLForIOS(true), "--passL:-lSDL2", 139 | "--passC:-mios-simulator-version-min=" & iOSMinVersion, "--passL:-mios-simulator-version-min=" & iOSMinVersion, 140 | "--passL:-fobjc-link-runtime" 141 | makeBundle() 142 | runAppInSimulator() 143 | 144 | task "ios", "Build for iOS": 145 | createSDLIncludeLink "nimcache" 146 | runNim "--passC:-Inimcache", "--cpu:arm", "--os:macosx", "-d:ios", "-d:iPhone", "-d:SDL_Static", 147 | "--passC:-isysroot", "--passC:" & iOSSDK, "--passL:-isysroot", "--passL:" & iOSSDK, 148 | "--passL:-L" & buildSDLForIOS(false), "--passL:-lSDL2", 149 | "--passC:-mios-version-min=" & iOSMinVersion, "--passL:-mios-version-min=" & iOSMinVersion, 150 | "--passL:-fobjc-link-runtime" 151 | echo "TODO: Codesign!" 152 | makeBundle() 153 | 154 | task "droid", "Build for android": 155 | let buildDir = makeAndroidBuildDir() 156 | let droidSrcDir = buildDir / "jni/src" 157 | runNim "--compileOnly", "--cpu:arm", "--os:linux", "-d:android", "-d:SDL_Static", "--nimcache:" & droidSrcDir 158 | cd buildDir 159 | putEnv "NIM_INCLUDE_DIR", expandTilde(nimIncludeDir) 160 | direShell androidSdk/"tools/android", "update", "project", "-p", ".", "-t", "android-20" 161 | direShell androidNdk/"ndk-build" 162 | direShell "ant", "debug" 163 | 164 | task "droid-install", "Install to android device.": 165 | cd makeAndroidBuildDir() 166 | direShell "ant", "debug", "install" 167 | 168 | -------------------------------------------------------------------------------- /res/Base.lproj/LaunchScreen.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yglukhov/nim-sdl-template/6ecfd693e89f54aa6681e7dc8a2796e7a44654d3/res/Base.lproj/LaunchScreen.nib -------------------------------------------------------------------------------- /res/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yglukhov/nim-sdl-template/6ecfd693e89f54aa6681e7dc8a2796e7a44654d3/res/Info.plist -------------------------------------------------------------------------------- /res/PkgInfo: -------------------------------------------------------------------------------- 1 | APPL???? -------------------------------------------------------------------------------- /src/application.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import sdl2 3 | import opengl 4 | import nimx.sdl_window 5 | import nimx.view 6 | import nimx.logging 7 | 8 | 9 | const isMobile = defined(ios) or defined(android) 10 | 11 | template c(a: string) = discard 12 | 13 | log "STARTING!" 14 | var mainWindow = when isMobile: 15 | newFullscreenSdlWindow() 16 | else: 17 | newSdlWindow(newRect(0, 0, 800, 600)) 18 | 19 | when not defined(ios) and not defined(android): 20 | loadExtensions() 21 | 22 | proc reshape(x, y: cint) = 23 | glViewport(0, 0, x, y) # Set the viewport to cover the new window 24 | 25 | var runGame = true 26 | 27 | proc eventFilter(userdata: pointer; event: ptr TEvent): Bool32 {.cdecl.} = 28 | case event.kind: 29 | of FingerMotion: 30 | log("finger motion") 31 | return False32 32 | of FingerDown: 33 | log("Finger down") 34 | return False32 35 | of FingerUp: 36 | log("Finger up") 37 | return False32 38 | of WindowEvent: 39 | let wndEv = cast[PWindowEvent](event) 40 | case wndEv.event: 41 | of WindowEvent_Resized: 42 | reshape(wndEv.data1, wndEv.data2) 43 | return False32 44 | else: discard 45 | of AppWillEnterBackground: 46 | when defined(ios): 47 | runGame = false 48 | 49 | else: discard 50 | log "Event: ", $event.kind 51 | return True32 52 | 53 | setEventHandler(eventFilter, nil) 54 | 55 | # Main loop 56 | var 57 | evt = defaultEvent 58 | 59 | while runGame: 60 | discard nextEvent(evt) 61 | if evt.kind == QuitEvent: 62 | runGame = false 63 | break 64 | 65 | sdl2.quit() 66 | 67 | -------------------------------------------------------------------------------- /src/nimx/button.nim: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yglukhov/nim-sdl-template/6ecfd693e89f54aa6681e7dc8a2796e7a44654d3/src/nimx/button.nim -------------------------------------------------------------------------------- /src/nimx/context.nim: -------------------------------------------------------------------------------- 1 | import view 2 | import opengl 3 | import unsigned 4 | import logging 5 | import sdl2 6 | 7 | type ShaderAttribute = enum 8 | saPosition 9 | 10 | 11 | const vertexShader = """ 12 | attribute vec4 position; 13 | 14 | void main() 15 | { 16 | gl_Position = position; 17 | } 18 | """ 19 | 20 | const fragmentShader = """ 21 | void main() 22 | { 23 | gl_FragColor = vec4(1.0, 0, 0, 0); 24 | } 25 | """ 26 | 27 | proc loadShader(shaderSrc: string, kind: GLenum): GLuint = 28 | # Create the shader object 29 | log "compile shader" 30 | result = glCreateShader(kind) 31 | if result == 0: 32 | return 33 | log "..." 34 | 35 | # Load the shader source 36 | var srcArray = [shaderSrc.cstring] 37 | glShaderSource(result, 1, cast[cstringArray](addr srcArray), nil) 38 | # Compile the shader 39 | glCompileShader(result) 40 | # Check the compile status 41 | var compiled: GLint 42 | glGetShaderiv(result, GL_COMPILE_STATUS, addr compiled) 43 | if compiled == 0: 44 | var infoLen: GLint 45 | glGetShaderiv(result, GL_INFO_LOG_LENGTH, addr infoLen) 46 | if infoLen > 1: 47 | var infoLog : cstring = cast[cstring](alloc(infoLen + 1)) 48 | glGetShaderInfoLog(result, infoLen, nil, infoLog) 49 | log "Error compiling shader: ", infoLog 50 | dealloc(infoLog) 51 | glDeleteShader(result) 52 | log "done" 53 | 54 | type PrimitiveType = enum 55 | ptTriangles = GL_TRIANGLES 56 | ptTriangleStrip = GL_TRIANGLE_STRIP 57 | ptTriangleFan = GL_TRIANGLE_FAN 58 | 59 | type GraphicsContext* = ref object of RootObj 60 | shaderProgram: GLuint 61 | 62 | var gCurrentContext: GraphicsContext 63 | 64 | proc newGraphicsContext*(): GraphicsContext = 65 | result.new() 66 | log "context init" 67 | when not defined(ios) and not defined(android): 68 | loadExtensions() 69 | log glGetString(GL_VERSION)[] 70 | 71 | 72 | let vShader = loadShader(vertexShader, GL_VERTEX_SHADER) 73 | if vShader == 0: 74 | log "No vshader!" 75 | 76 | 77 | 78 | log "create program" 79 | result.shaderProgram = glCreateProgram() 80 | if result.shaderProgram == 0: 81 | log "Could not create program: ", glGetError() 82 | log "created program" 83 | result.shaderProgram.glAttachShader(vShader) 84 | result.shaderProgram.glAttachShader(loadShader(fragmentShader, GL_FRAGMENT_SHADER)) 85 | log "link" 86 | result.shaderProgram.glLinkProgram() 87 | var linked : GLint 88 | glGetProgramiv(result.shaderProgram, GL_LINK_STATUS, addr linked) 89 | if linked == 0: 90 | log "Could not link!" 91 | 92 | result.shaderProgram.glBindAttribLocation(GLuint(saPosition), "position") 93 | result.shaderProgram.glUseProgram() 94 | log "RUNNING" 95 | 96 | 97 | proc setCurrentContext*(c: GraphicsContext): GraphicsContext {.discardable.} = 98 | result = gCurrentContext 99 | gCurrentContext = c 100 | 101 | proc currentContext*(): GraphicsContext = gCurrentContext 102 | 103 | proc drawVertexes*(c: GraphicsContext, componentCount: int, points: openarray[Coord], pt: PrimitiveType) = 104 | assert(points.len mod componentCount == 0) 105 | glEnableVertexAttribArray(GLuint(saPosition)) 106 | glVertexAttribPointer(GLuint(saPosition), GLint(componentCount), cGL_FLOAT, GLboolean(GL_FALSE), 0, cast[pointer](points)) 107 | #glVertexPointer(GLint(componentCount), GLenum(cGL_FLOAT), 0, cast[pointer](points)) 108 | glDrawArrays(cast[GLenum](pt), 0, GLsizei(points.len / componentCount)) 109 | 110 | #proc drawVertexes*(c: GraphicsContext, points: openarray[Point], pt: PrimitiveType) = 111 | # glVertexPointer(2, cGL_FLOAT, 0, cast[pointer](points)) 112 | # glDrawArrays(cast[GLenum](pt), 0, GLsizei(points.len)) 113 | 114 | proc drawRect*(c: GraphicsContext, r: view.Rect) = 115 | let points = [r.minX, r.minY, 116 | r.maxX, r.minY, 117 | r.maxX, r.maxY, 118 | r.minX, r.maxY] 119 | c.drawVertexes(2, points, ptTriangleFan) 120 | 121 | -------------------------------------------------------------------------------- /src/nimx/logging.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | 3 | # Support logging on iOS and android 4 | when defined(macosx) or defined(ios): 5 | {.emit: """ 6 | 7 | #include 8 | extern void NSLog(CFStringRef format, ...); 9 | 10 | """.} 11 | 12 | proc NSLog_imported(a: cstring) = 13 | {.emit: "NSLog(CFSTR(\"%s\"), a);" .} 14 | 15 | proc log*(a: varargs[string, `$`]) = NSLog_imported(a.join()) 16 | elif defined(android): 17 | {.emit: """ 18 | #include 19 | """.} 20 | 21 | proc droid_log_imported(a: cstring) = 22 | {.emit: """__android_log_print(ANDROID_LOG_INFO, "NIM_APP", a);""".} 23 | proc log*(a: varargs[string, `$`]) = droid_log_imported(a.join()) 24 | else: 25 | proc log*(a: varargs[string, `$`]) = echo a 26 | 27 | -------------------------------------------------------------------------------- /src/nimx/sdl_window.nim: -------------------------------------------------------------------------------- 1 | import window 2 | import sdl2 3 | import os 4 | import logging 5 | import view 6 | import opengl 7 | import context 8 | 9 | type SdlWindow = ref object of Window 10 | impl: PWindow 11 | sdlGlContext: PGLContext 12 | renderingContext: GraphicsContext 13 | 14 | var allWindows : seq[SdlWindow] = @[] 15 | 16 | #extern DECLSPEC int SDLCALL SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam); 17 | #proc iPhoneSetAnimationCallback(window: PWindow, interval: int, callback: proc(p: ptr RootObj) {.cdecl.}, callbackParam: ptr RootObj): int {.importc: "SDL_iPhoneSetAnimationCallback", header: "".} 18 | 19 | proc animationCallback(p: pointer) {.cdecl.} = 20 | cast[SdlWindow](p).draw() 21 | 22 | proc init(w: SdlWindow, r: view.Rect) = 23 | if w.impl == nil: 24 | log("Could not create window!") 25 | quit 1 26 | procCall init(cast[Window](w), r) 27 | w.sdlGlContext = w.impl.GL_CreateContext() 28 | w.renderingContext = newGraphicsContext() 29 | 30 | when defined(ios): 31 | discard iPhoneSetAnimationCallback(w.impl, 0, animationCallback, cast[pointer](w)) 32 | allWindows.add(w) 33 | discard w.impl.SetData("__nimx_wnd", cast[pointer](w)) 34 | 35 | proc newFullscreenSdlWindow*(): SdlWindow = 36 | result.new() 37 | 38 | log "Set profile" 39 | discard GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0x0004) 40 | discard GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2) 41 | 42 | var displayMode : TDisplayMode 43 | discard GetDesktopDisplayMode(0, displayMode) 44 | let flags = SDL_WINDOW_OPENGL or SDL_WINDOW_FULLSCREEN 45 | log "Creating window" 46 | result.impl = CreateWindow(getAppFilename(), 0, 0, displayMode.w, displayMode.h, flags) 47 | result.init(newRect(0, 0, Coord(displayMode.w), Coord(displayMode.h))) 48 | 49 | proc newSdlWindow*(r: view.Rect, title: string = nil): SdlWindow = 50 | when defined(ios): 51 | return newFullscreenSdlWindow() 52 | else: 53 | result.new() 54 | let t = if title == nil: getAppFilename() else: title 55 | result.impl = CreateWindow(t, cint(r.x), cint(r.y), cint(r.width), cint(r.height), SDL_WINDOW_OPENGL or SDL_WINDOW_RESIZABLE) 56 | result.init(newRect(0, 0, r.width, r.height)) 57 | 58 | var r = 0.0 59 | var g = 1.0 60 | var b = 0.0 61 | 62 | var dr = 0.02 63 | var dg = 0.03 64 | var db = 0.01 65 | 66 | 67 | method draw(w: SdlWindow) = 68 | glClearColor(r, g, b, 1.0) 69 | glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) # Clear color and depth buffers 70 | var oldContext = setCurrentContext(w.renderingContext) 71 | 72 | r += dr 73 | g += dg 74 | b += db 75 | 76 | if r > 1.0: 77 | r = 1.0 78 | dr = -dr 79 | elif r < 0.0: 80 | r = 0.0 81 | dr = -dr 82 | if g > 1.0: 83 | g = 1.0 84 | dg = -dg 85 | elif g < 0.0: 86 | g = 0.0 87 | dg = -dg 88 | if b > 1.0: 89 | b = 1.0 90 | db = -db 91 | elif b < 0.0: 92 | b = 0.0 93 | db = -db 94 | currentContext().drawRect(newRect(50, 50, 100, 100)) 95 | 96 | w.drawSubviews() 97 | w.impl.GL_SwapWindow() # Swap the front and back frame buffers (double buffering) 98 | setCurrentContext(oldContext) 99 | 100 | proc waitOrPollEvent(evt: var TEvent): auto = 101 | when defined(ios): 102 | WaitEvent(evt) 103 | else: 104 | PollEvent(evt) 105 | 106 | proc handleSdlEvent(w: SdlWindow, e: TWindowEvent) = 107 | case e.event: 108 | of WindowEvent_Resized: 109 | w.onResize(newSize(cast[Coord](e.data1), cast[Coord](e.data2))) 110 | else: discard 111 | 112 | var eventHandler: TEventFilter 113 | var eventHandlerUserData: pointer 114 | 115 | proc eventFilter(userdata: pointer; event: ptr TEvent): Bool32 {.cdecl.} = 116 | case event.kind: 117 | of FingerMotion: 118 | #log("finger motion") 119 | return False32 120 | of FingerDown: 121 | log("Finger down") 122 | return False32 123 | of FingerUp: 124 | log("Finger up") 125 | return False32 126 | of WindowEvent: 127 | let wndEv = cast[PWindowEvent](event) 128 | let sdlWndId = wndEv.windowID 129 | let sdlWin = GetWindowFromID(sdlWndId) 130 | if sdlWin != nil: 131 | let wnd = cast[SdlWindow](sdlWin.GetData("__nimx_wnd")) 132 | if wnd != nil: 133 | wnd.handleSdlEvent(wndEv[]) 134 | of AppWillEnterBackground: 135 | when defined(ios): 136 | #runGame = false 137 | discard 138 | 139 | else: discard 140 | log "Event: ", $event.kind 141 | return True32 142 | 143 | proc setEventHandler*(filter: TEventFilter; userdata: pointer) = 144 | eventHandler = filter 145 | eventHandlerUserData = userdata 146 | SetEventFilter(eventFilter, nil) 147 | 148 | 149 | method onResize*(w: SdlWindow, newSize: Size) = 150 | glViewport(0, 0, GLSizei(newSize.width), GLsizei(newSize.height)) 151 | 152 | # Framerate limiter 153 | let MAXFRAMERATE: uint32 = 20 # milli seconds 154 | var frametime: uint32 155 | 156 | proc limitFramerate() = 157 | var now = GetTicks() 158 | if frametime > now: 159 | Delay(frametime - now) 160 | frametime = frametime + MAXFRAMERATE 161 | 162 | proc nextEvent*(evt: var TEvent): bool = 163 | PumpEvents() 164 | while waitOrPollEvent(evt): 165 | var handled = false 166 | if evt.kind == WindowEvent: 167 | let winEvt = cast[PWindowEvent](addr evt) 168 | let sdlWndId = cast[PWindowEvent](addr evt).windowID 169 | let sdlWin = GetWindowFromID(sdlWndId) 170 | if sdlWin != nil: 171 | let wnd = cast[SdlWindow](sdlWin.GetData("__nimx_wnd")) 172 | if wnd != nil: 173 | wnd.handleSdlEvent(winEvt[]) 174 | handled = true 175 | if not handled: 176 | return false 177 | 178 | when not defined(ios): 179 | for wnd in allWindows: 180 | wnd.draw() 181 | limitFramerate() 182 | 183 | -------------------------------------------------------------------------------- /src/nimx/view.nim: -------------------------------------------------------------------------------- 1 | import opengl 2 | import typetraits 3 | #import graphics 4 | 5 | type Coord* = float32 6 | 7 | type Point* = tuple[x, y: Coord] 8 | type Size* = tuple[width, height: Coord] 9 | type Rect* = tuple[origin: Point, size: Size] 10 | 11 | proc x*(r: Rect): Coord = r.origin.x 12 | proc y*(r: Rect): Coord = r.origin.y 13 | proc width*(r: Rect): Coord = r.size.width 14 | proc height*(r: Rect): Coord = r.size.height 15 | 16 | proc minX*(r: Rect): Coord = r.x 17 | proc maxX*(r: Rect): Coord = r.x + r.width 18 | proc minY*(r: Rect): Coord = r.y 19 | proc maxY*(r: Rect): Coord = r.y + r.height 20 | 21 | type ButtonState = enum 22 | bsUnknown, bsUp, bsDown 23 | 24 | type EventKind = enum 25 | ekMouseMove, ekMouseAction 26 | 27 | type MouseEvent = tuple[position: Point, kind: EventKind, state: ButtonState] 28 | 29 | type 30 | View* = ref TView 31 | TView = object of RootObj 32 | frame: Rect 33 | bounds: Rect 34 | subviews: seq[View] 35 | superview: PView 36 | 37 | PView = ptr TView 38 | 39 | proc newRect*(x, y, w, h: Coord): Rect = 40 | result.origin.x = x 41 | result.origin.y = y 42 | result.size.width = w 43 | result.size.height = h 44 | 45 | proc newSize*(w, h: Coord): Size = 46 | result.width = w 47 | result.height = h 48 | 49 | proc new*(a: typedesc[Rect], x, y, w, h: Coord): Rect = 50 | result.origin.x = x 51 | result.origin.y = y 52 | result.size.width = w 53 | result.size.height = h 54 | 55 | method init*(v: View, frame: Rect) = 56 | v.frame = frame 57 | v.bounds = Rect.new(0, 0, frame.width, frame.height) 58 | v.subviews = @[] 59 | 60 | proc convertCoordinates*(p: Point, fromView, toView: View): Point = 61 | if fromView == toView: return p 62 | if fromView == nil: # p is screen coordinates 63 | discard 64 | return p 65 | 66 | proc convertCoordinates*(r: Rect, fromView, toView: View): Rect = 67 | r 68 | 69 | method draw*(view: View) 70 | 71 | method drawSelf*(view: View) = 72 | discard 73 | 74 | method drawSubviews*(view: View) = 75 | for i in view.subviews: 76 | i.draw() 77 | 78 | method draw*(view: View) = 79 | view.drawSelf() 80 | view.drawSubviews() 81 | 82 | method handleMouseEventRecursive(v: View, e: MouseEvent, translatedCoords: Point): bool = 83 | for i in v.subviews: 84 | discard 85 | -------------------------------------------------------------------------------- /src/nimx/window.nim: -------------------------------------------------------------------------------- 1 | 2 | import view 3 | 4 | type Window* = ref object of View 5 | 6 | 7 | method draw*(win: Window) = 8 | discard 9 | 10 | 11 | method onResize*(w: Window, newSize: Size) = 12 | discard 13 | --------------------------------------------------------------------------------