From 1dff746b0260f7ea423d961ae0c8a55f2a71af72 Mon Sep 17 00:00:00 2001 From: quddusc Date: Mon, 1 Jul 2013 16:57:26 -0700 Subject: cherrypick from jb-mr2-docs docs: Training class for game controllers. Change-Id: I697770aee8604c965c3730691459c1e8f10705da Change-Id: I40b79aee2302f1e32a9e8bc11d0b0415dcbe7705 --- docs/downloads/training/ControllerSample.zip | Bin 0 -> 370590 bytes .../training/backward-compatible-inputmanager.png | Bin 0 -> 34560 bytes .../images/training/game-controller-profiles.png | Bin 0 -> 74202 bytes .../training/game-controllers/compatibility.jd | 643 +++++ .../training/game-controllers/controller-input.jd | 656 +++++ docs/html/training/game-controllers/index.jd | 60 + .../game-controllers/multiple-controllers.jd | 130 + docs/html/training/training_toc.cs | 25 +- .../backward-compatible-inputmanager.graffle | 1076 ++++++++ .../game-controller-profiles.graffle | 2773 ++++++++++++++++++++ 10 files changed, 5361 insertions(+), 2 deletions(-) create mode 100644 docs/downloads/training/ControllerSample.zip create mode 100644 docs/html/images/training/backward-compatible-inputmanager.png create mode 100644 docs/html/images/training/game-controller-profiles.png create mode 100644 docs/html/training/game-controllers/compatibility.jd create mode 100644 docs/html/training/game-controllers/controller-input.jd create mode 100644 docs/html/training/game-controllers/index.jd create mode 100644 docs/html/training/game-controllers/multiple-controllers.jd create mode 100644 docs/image_sources/training/game-controllers/backward-compatible-inputmanager.graffle create mode 100644 docs/image_sources/training/game-controllers/game-controller-profiles.graffle diff --git a/docs/downloads/training/ControllerSample.zip b/docs/downloads/training/ControllerSample.zip new file mode 100644 index 000000000000..b09686195150 Binary files /dev/null and b/docs/downloads/training/ControllerSample.zip differ diff --git a/docs/html/images/training/backward-compatible-inputmanager.png b/docs/html/images/training/backward-compatible-inputmanager.png new file mode 100644 index 000000000000..fdac78a14526 Binary files /dev/null and b/docs/html/images/training/backward-compatible-inputmanager.png differ diff --git a/docs/html/images/training/game-controller-profiles.png b/docs/html/images/training/game-controller-profiles.png new file mode 100644 index 000000000000..f8c1cbbace8d Binary files /dev/null and b/docs/html/images/training/game-controller-profiles.png differ diff --git a/docs/html/training/game-controllers/compatibility.jd b/docs/html/training/game-controllers/compatibility.jd new file mode 100644 index 000000000000..f68ab1ae1278 --- /dev/null +++ b/docs/html/training/game-controllers/compatibility.jd @@ -0,0 +1,643 @@ +page.title=Supporting Controllers Across Android Versions +trainingnavtop=true + +@jd:body + + +
+ +
+ +

If you are supporting game controllers in your game, it's your responsibility +to make sure that your game responds to controllers consistently across devices +running on different versions of Android. This lets your game reach a wider +audience, and your players can enjoy a seamless gameplay experience with +their controllers even when they switch or upgrade their Android devices.

+ +

This lesson demonstrates how to use APIs available in Android 4.1 and higher +in a backward compatible way, enabling your game to support the following +features on devices running Android 2.3 and higher:

+ + +

The examples in this lesson are based on the reference implementation +provided by the sample {@code ControllerSample.zip} available for download +above. This sample shows how to implement the {@code InputManagerCompat} +interface to support different versions of Android. To compile the sample, you +must use Android 4.1 (API level 16) or higher. Once compiled, the sample app +runs on any device running Android 2.3 (API level 9) or higher as the build +target. +

+ +

Prepare to Abstract APIs for Game Controller Support

+

Suppose you want to be able to determine if a game controller's connection +status has changed on devices running on Android 2.3 (API level 9). However, +the APIs are only available in Android 4.1 (API level 16) and higher, so you +need to provide an implementation that supports Android 4.1 and higher while +providing a fallback mechanism that supports Android 2.3 up to Android 4.0.

+ +

To help you determine which features require such a fallback mechanism for + older versions, table 1 lists the differences in game controller support + between Android 2.3 (API level 9), 3.1 (API level 12), and 4.1 (API level + 16).

+ +

+Table 1. APIs for game controller support across +different Android versions. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Controller InformationController APIAPI level 9API level 12API level 16
Device Identification{@link android.hardware.input.InputManager#getInputDeviceIds()}  
{@link android.hardware.input.InputManager#getInputDevice(int) +getInputDevice()}  
{@link android.view.InputDevice#getVibrator()}  
{@link android.view.InputDevice#SOURCE_JOYSTICK} 
{@link android.view.InputDevice#SOURCE_GAMEPAD} 
Connection Status{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceAdded(int) onInputDeviceAdded()}  
{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged(int) onInputDeviceChanged()}  
{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved(int) onInputDeviceRemoved()}  
Input Event IdentificationD-pad press ( +{@link android.view.KeyEvent#KEYCODE_DPAD_UP}, +{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}, +{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}, +{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}, +{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER})
Gamepad button press ( +{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBL BUTTON_THUMBL}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBR BUTTON_THUMBR}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_R1 BUTTON_R1}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_L1 BUTTON_L1}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_R2 BUTTON_R2}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_L2 BUTTON_L2}) 
Joystick and hat switch movement ( +{@link android.view.MotionEvent#AXIS_X}, +{@link android.view.MotionEvent#AXIS_Y}, +{@link android.view.MotionEvent#AXIS_Z}, +{@link android.view.MotionEvent#AXIS_RZ}, +{@link android.view.MotionEvent#AXIS_HAT_X}, +{@link android.view.MotionEvent#AXIS_HAT_Y}) 
Analog trigger press ( +{@link android.view.MotionEvent#AXIS_LTRIGGER}, +{@link android.view.MotionEvent#AXIS_RTRIGGER}) 
+ +

You can use abstraction to build version-aware game controller support that +works across platforms. This approach involves the following steps:

+
    +
  1. Define an intermediary Java interface that abstracts the implementation of +the game controller features required by your game.
  2. +
  3. Create a proxy implementation of your interface that uses APIs in Android +4.1 and higher.
  4. +
  5. Create a custom implementation of your interface that uses APIs available +between Android 2.3 up to Android 4.0.
  6. +
  7. Create the logic for switching between these implementations at runtime, +and begin using the interface in your game.
  8. +
+ +

For an overview of how abstraction can be used to ensure that applications +can work in a backward compatible way across different versions of Android, see +Creating +Backward-Compatible UIs. +

+ +

Add an Interface for Backward Compatibility

+ +

To provide backward compatibility, you can create a custom interface then +add version-specific implementations. One advantage of this approach is that it +lets you mirror the public interfaces on Android 4.1 (API level 16) that +support game controllers.

+
+// The InputManagerCompat interface is a reference example.
+// The full code is provided in the ControllerSample.zip sample.
+public interface InputManagerCompat {
+    ...
+    public InputDevice getInputDevice(int id);
+    public int[] getInputDeviceIds();
+
+    public void registerInputDeviceListener(
+            InputManagerCompat.InputDeviceListener listener,
+            Handler handler);
+    public void unregisterInputDeviceListener(
+            InputManagerCompat.InputDeviceListener listener);
+
+    public void onGenericMotionEvent(MotionEvent event);
+
+    public void onPause();
+    public void onResume();
+
+    public interface InputDeviceListener {
+        void onInputDeviceAdded(int deviceId);
+        void onInputDeviceChanged(int deviceId);
+        void onInputDeviceRemoved(int deviceId);
+    }
+    ...
+}
+
+

The {@code InputManagerCompat} interface provides the following methods:

+
+
{@code getInputDevice()}
+
Mirrors {@link android.hardware.input.InputManager#getInputDevice(int) +getInputDevice()}. Obtains the {@link android.view.InputDevice} +object that represents the capabilities of a game controller.
+
{@code getInputDeviceIds()}
+
Mirrors {@link android.hardware.input.InputManager#getInputDeviceIds() +getInputDeviceIds()}. Returns an array of integers, each of +which is an ID for a different input device. This is useful if you're building +a game that supports multiple players and you want to detect how many +controllers are connected.
+
{@code registerInputDeviceListener()}
+
Mirrors {@link android.hardware.input.InputManager#registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler) +registerInputDeviceListener()}. Lets you register to be informed when a new +device is added, changed, or removed.
+
{@code unregisterInputDeviceListener()}
+
Mirrors {@link android.hardware.input.InputManager#unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener) unregisterInputDeviceListener()}. +Unregisters an input device listener.
+
{@code onGenericMotionEvent()}
+
Mirrors {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()}. Lets your game intercept and handle +{@link android.view.MotionEvent} objects and axis values that represent events +such as joystick movements and analog trigger presses.
+
{@code onPause()}
+
Stops polling for game controller events when the +main activity is paused, or when the game no longer has focus.
+
{@code onResume()}
+
Starts polling for game controller events when the +main activity is resumed, or when the game is started and runs in the +foreground.
+
{@code InputDeviceListener}
+
Mirrors the {@link android.hardware.input.InputManager.InputDeviceListener} +interface. Lets your game know when a game controller has been added, changed, or +removed.
+
+

Next, create implementations for {@code InputManagerCompat} that work +across different platform versions. If your game is running on Android 4.1 or +higher and calls an {@code InputManagerCompat} method, the proxy implementation +calls the equivalent method in {@link android.hardware.input.InputManager}. +However, if your game is running on Android 2.3 up to Android 4.0, the custom implementation processes calls to {@code InputManagerCompat} methods by using +only APIs introduced no later than Android 2.3. Regardless of which +version-specific implementation is used at runtime, the implementation passes +the call results back transparently to the game.

+ + +

+ Figure 1. Class diagram of interface and version-specific +implementations. +

+ +

Implement the Interface on Android 4.1 and Higher

+

{@code InputManagerCompatV16} is an implementation of the +{@code InputManagerCompat} interface that proxies method calls to an +actual {@link android.hardware.input.InputManager} and {@link +android.hardware.input.InputManager.InputDeviceListener}. The +{@link android.hardware.input.InputManager} is obtained from the system +{@link android.content.Context}.

+ +
+// The InputManagerCompatV16 class is a reference implementation.
+// The full code is provided in the ControllerSample.zip sample.
+public class InputManagerV16 implements InputManagerCompat {
+
+    private final InputManager mInputManager;
+    private final Map mListeners;
+
+    public InputManagerV16(Context context) {
+        mInputManager = (InputManager)
+                context.getSystemService(Context.INPUT_SERVICE);
+        mListeners = new HashMap();
+    }
+
+    @Override
+    public InputDevice getInputDevice(int id) {
+        return mInputManager.getInputDevice(id);
+    }
+
+    @Override
+    public int[] getInputDeviceIds() {
+        return mInputManager.getInputDeviceIds();
+    }
+
+    static class V16InputDeviceListener implements
+            InputManager.InputDeviceListener {
+        final InputManagerCompat.InputDeviceListener mIDL;
+
+        public V16InputDeviceListener(InputDeviceListener idl) {
+            mIDL = idl;
+        }
+
+        @Override
+        public void onInputDeviceAdded(int deviceId) {
+            mIDL.onInputDeviceAdded(deviceId);
+        }
+
+        // Do the same for device change and removal
+        ...
+    }
+
+    @Override
+    public void registerInputDeviceListener(InputDeviceListener listener,
+            Handler handler) {
+        V16InputDeviceListener v16Listener = new
+                V16InputDeviceListener(listener);
+        mInputManager.registerInputDeviceListener(v16Listener, handler);
+        mListeners.put(listener, v16Listener);
+    }
+
+    // Do the same for unregistering an input device listener
+    ...
+
+    @Override
+    public void onGenericMotionEvent(MotionEvent event) {
+        // unused in V16
+    }
+
+    @Override
+    public void onPause() {
+        // unused in V16
+    }
+
+    @Override
+    public void onResume() {
+        // unused in V16
+    }
+
+}
+
+ +

Implementing the Interface on Android 2.3 up to Android 4.0

+ +

The {@code InputManagerV9} implementation uses APIs introduced no later +than Android 2.3. To create an implementation of {@code +InputManagerCompat} that supports Android 2.3 up to Android 4.0, you can use +the following objects: +

+ +
+// The InputManagerCompatV9 class is a reference implementation.
+// The full code is provided in the ControllerSample.zip sample.
+public class InputManagerV9 implements InputManagerCompat {
+    private final SparseArray mDevices;
+    private final Map mListeners;
+    private final Handler mDefaultHandler;
+    …
+
+    public InputManagerV9() {
+        mDevices = new SparseArray();
+        mListeners = new HashMap();
+        mDefaultHandler = new PollingMessageHandler(this);
+    }
+}
+
+ +

Implement a {@code PollingMessageHandler} object that extends +{@link android.os.Handler}, and override the +{@link android.os.Handler#handleMessage(android.os.Message) handleMessage()} +method. This method checks if an attached game controller has been +disconnected and notifies registered listeners.

+ +
+private static class PollingMessageHandler extends Handler {
+    private final WeakReference mInputManager;
+
+    PollingMessageHandler(InputManagerV9 im) {
+        mInputManager = new WeakReference(im);
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        super.handleMessage(msg);
+        switch (msg.what) {
+            case MESSAGE_TEST_FOR_DISCONNECT:
+                InputManagerV9 imv = mInputManager.get();
+                if (null != imv) {
+                    long time = SystemClock.elapsedRealtime();
+                    int size = imv.mDevices.size();
+                    for (int i = 0; i < size; i++) {
+                        long[] lastContact = imv.mDevices.valueAt(i);
+                        if (null != lastContact) {
+                            if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
+                                // check to see if the device has been
+                                // disconnected
+                                int id = imv.mDevices.keyAt(i);
+                                if (null == InputDevice.getDevice(id)) {
+                                    // Notify the registered listeners
+                                    // that the game controller is disconnected
+                                    ...
+                                    imv.mDevices.remove(id);
+                                } else {
+                                    lastContact[0] = time;
+                                }
+                            }
+                        }
+                    }
+                    sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
+                            CHECK_ELAPSED_TIME);
+                }
+                break;
+        }
+    }
+}
+
+ +

To start and stop polling for game controller disconnection, override +these methods:

+
+private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
+private static final long CHECK_ELAPSED_TIME = 3000L;
+
+@Override
+public void onPause() {
+    mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
+}
+
+@Override
+public void onResume() {
+    mDefaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
+            CHECK_ELAPSED_TIME);
+}
+
+ +

To detect that an input device has been added, override the +{@code onGenericMotionEvent()} method. When the system reports a motion event, +check if this event came from a device ID that is already tracked, or from a +new device ID. If the device ID is new, notify registered listeners.

+ +
+@Override
+public void onGenericMotionEvent(MotionEvent event) {
+    // detect new devices
+    int id = event.getDeviceId();
+    long[] timeArray = mDevices.get(id);
+    if (null == timeArray) {
+        // Notify the registered listeners that a game controller is added
+        ...
+        timeArray = new long[1];
+        mDevices.put(id, timeArray);
+    }
+    long time = SystemClock.elapsedRealtime();
+    timeArray[0] = time;
+}
+
+ +

Notification of listeners is implemented by using the +{@link android.os.Handler} object to send a {@code DeviceEvent} +{@link java.lang.Runnable} object to the message queue. The {@code DeviceEvent} +contains a reference to an {@code InputManagerCompat.InputDeviceListener}. When +the {@code DeviceEvent} runs, the appropriate callback method of the listener +is called to signal if the game controller was added, changed, or removed. +

+ +
+@Override
+public void registerInputDeviceListener(InputDeviceListener listener,
+        Handler handler) {
+    mListeners.remove(listener);
+    if (handler == null) {
+        handler = mDefaultHandler;
+    }
+    mListeners.put(listener, handler);
+}
+
+@Override
+public void unregisterInputDeviceListener(InputDeviceListener listener) {
+    mListeners.remove(listener);
+}
+
+private void notifyListeners(int why, int deviceId) {
+    // the state of some device has changed
+    if (!mListeners.isEmpty()) {
+        for (InputDeviceListener listener : mListeners.keySet()) {
+            Handler handler = mListeners.get(listener);
+            DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId,
+                    listener);
+            handler.post(odc);
+        }
+    }
+}
+
+private static class DeviceEvent implements Runnable {
+    private int mMessageType;
+    private int mId;
+    private InputDeviceListener mListener;
+    private static Queue sObjectQueue =
+            new ArrayDeque();
+    ...
+
+    static DeviceEvent getDeviceEvent(int messageType, int id,
+            InputDeviceListener listener) {
+        DeviceEvent curChanged = sObjectQueue.poll();
+        if (null == curChanged) {
+            curChanged = new DeviceEvent();
+        }
+        curChanged.mMessageType = messageType;
+        curChanged.mId = id;
+        curChanged.mListener = listener;
+        return curChanged;
+    }
+
+    @Override
+    public void run() {
+        switch (mMessageType) {
+            case ON_DEVICE_ADDED:
+                mListener.onInputDeviceAdded(mId);
+                break;
+            case ON_DEVICE_CHANGED:
+                mListener.onInputDeviceChanged(mId);
+                break;
+            case ON_DEVICE_REMOVED:
+                mListener.onInputDeviceRemoved(mId);
+                break;
+            default:
+                // Handle unknown message type
+                ...
+                break;
+        }
+        // Put this runnable back in the queue
+        sObjectQueue.offer(this);
+    }
+}
+
+ +

You now have two implementations of {@code InputManagerCompat}: one that +works on devices running Android 4.1 and higher, and another +that works on devices running Android 2.3 up to Android 4.0.

+ +

Use the Version-Specific Implementation

+

The version-specific switching logic is implemented in a class that acts as +a factory.

+
+public static class Factory {
+    public static InputManagerCompat getInputManager(Context context) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            return new InputManagerV16(context);
+        } else {
+            return new InputManagerV9();
+        }
+    }
+}
+
+

Now you can simply instantiate an {@code InputManagerCompat} object and +register an {@code InputManagerCompat.InputDeviceListener} in your main +{@link android.view.View}. Because of the version-switching logic you set +up, your game automatically uses the implementation that's appropriate for the +version of Android the device is running.

+
+public class GameView extends View implements InputDeviceListener {
+    private InputManagerCompat mInputManager;
+    ...
+
+    public GameView(Context context, AttributeSet attrs) {
+        mInputManager =
+                InputManagerCompat.Factory.getInputManager(this.getContext());
+        mInputManager.registerInputDeviceListener(this, null);
+        ...
+    }
+}
+
+

Next, override the +{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()} method in your main view, as described in +Handle a MotionEvent from a Game +Controller. Your game should now be able to process game controller events +consistently on devices running Android 2.3 (API level 9) and higher. +

+

+@Override
+public boolean onGenericMotionEvent(MotionEvent event) {
+    mInputManager.onGenericMotionEvent(event);
+
+    // Handle analog input from the controller as normal
+    ...
+    return super.onGenericMotionEvent(event);
+}
+
+

You can find a complete implementation of this compatibility code in the +{@code GameView} class provided in the sample {@code ControllerSample.zip} +available for download above.

\ No newline at end of file diff --git a/docs/html/training/game-controllers/controller-input.jd b/docs/html/training/game-controllers/controller-input.jd new file mode 100644 index 000000000000..2c50ae1a67fb --- /dev/null +++ b/docs/html/training/game-controllers/controller-input.jd @@ -0,0 +1,656 @@ +page.title=Handling Controller Actions +trainingnavtop=true + +@jd:body + + +
+
+ +

This lesson teaches you to

+
    +
  1. Verify a Game Controller is Connected
  2. +
  3. Process Gamepad Button Presses +
  4. +
  5. Process Directional Pad Input +
  6. +
  7. Process Joystick Movements +
  8. +
+ +

Try it out

+
+ Download the sample +

ControllerSample.zip

+
+ +
+
+ +

At the system level, Android reports input event codes from game controllers +as Android key codes and axis values. In your game, you can receive these codes +and values and convert them to specific in-game actions.

+ +

When players physically connect or wirelessly pair a game controller to +their Android-powered devices, the system auto-detects the controller +as an input device and starts reporting its input events. Your game can receive +these input events by implementing the following callback methods in your active +{@link android.app.Activity} or focused {@link android.view.View} (you should +implement the callbacks for either the {@link android.app.Activity} or +{@link android.view.View}, but not both):

+ + + +

The recommended approach is to capture the events from the + specific {@link android.view.View} object that the user interacts with. + Inspect the following objects provided by the callbacks to get information + about the type of input event received:

+ +
+
{@link android.view.KeyEvent}
+
An object that describes directional +pad (D-pad) and gamepad button events. Key events are accompanied by a +key code that indicates the specific button triggered, such as +{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN DPAD_DOWN} +or {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}. You can obtain the +key code by calling {@link android.view.KeyEvent#getKeyCode()} or from key +event callbacks such as +{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()}. +
+
{@link android.view.MotionEvent}
+
An object that describes input from joystick and shoulder trigger + movements. Motion events are accompanied by an action code and a set of +axis values. The action code specifies the state change that occurred +such as a joystick being moved. The axis values describe the position and other +movement properties for a specific physical control, such as +{@link android.view.MotionEvent#AXIS_X} or +{@link android.view.MotionEvent#AXIS_RTRIGGER}. You can obtain the action code +by calling {@link android.view.MotionEvent#getAction()} and the axis value by +calling {@link android.view.MotionEvent#getAxisValue(int) getAxisValue()}. +
+
+

This lesson focuses on how you can handle input from the most common types of +physical controls (gamepad buttons, directional pads, and +joysticks) in a game screen by implementing the above-mentioned +{@link android.view.View} callback methods and processing +{@link android.view.KeyEvent} and {@link android.view.MotionEvent} objects.

+ +

Verify a Game Controller is Connected

+

When reporting input events, Android does not distinguish +between events that came from a non-game controller device and events that came +from a game controller. For example, a touch screen action generates an +{@link android.view.MotionEvent#AXIS_X} event that represents the X +coordinate of the touch surface, but a joystick generates an {@link android.view.MotionEvent#AXIS_X} event that represents the X position of the joystick. If +your game cares about handling game-controller input, you should first check +that the input event comes from a relevant source type.

+

To verify that a connected input device is a game controller, call +{@link android.view.InputDevice#getSources()} to obtain a combined bit field of +input source types supported on that device. You can then test to see if +the following fields are set:

+ +

The following code snippet shows a helper method that lets you check whether + the connected input devices are game controllers. If so, the method retrieves + the device IDs for the game controllers. You can then associate each device + ID with a player in your game, and process game actions for each connected + player separately. To learn more about supporting multiple game controllers + that are simultaneously connected on the same Android device, see + Supporting Multiple Game Controllers.

+
+public ArrayList getGameControllerIds() {
+    ArrayList gameControllerDeviceIds = new ArrayList();
+    int[] deviceIds = InputDevice.getDeviceIds();
+    for (int deviceId : deviceIds) {
+        InputDevice dev = InputDevice.getDevice(deviceId);
+        int sources = dev.getSources();
+
+        // Verify that the device has gamepad buttons, control sticks, or both.
+        if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
+                || ((sources & InputDevice.SOURCE_JOYSTICK)
+                == InputDevice.SOURCE_JOYSTICK)) {
+            // This device is a game controller. Store its device ID.
+            if (!gameControllerDeviceIds.contains(deviceId)) {
+                gameControllerDeviceIds.add(deviceId);
+            }
+        }
+    }
+    return gameControllerDeviceIds;
+}
+
+

Additionally, you might want to check for individual input capabilities +supported by a connected game controller. This might be useful, for example, if +you want your game to use only input from the set of physical controls it +understands.

+

To detect if a specific key code or axis code is supported by a connected +game controller, use these techniques:

+ + +

Process Gamepad Button Presses

+

Figure 1 shows how Android maps key codes and axis values to the physical +controls on most game controllers.

+ +

+ Figure 1. Profile for a generic game controller. +

+

The callouts in the figure refer to the following:

+
+
    +
  1. {@link android.view.MotionEvent#AXIS_HAT_X}, +{@link android.view.MotionEvent#AXIS_HAT_Y}, +{@link android.view.KeyEvent#KEYCODE_DPAD_UP DPAD_UP}, +{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN DPAD_DOWN}, +{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT DPAD_LEFT}, +{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT DPAD_RIGHT} +
  2. +
  3. {@link android.view.MotionEvent#AXIS_X}, +{@link android.view.MotionEvent#AXIS_Y}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBL BUTTON_THUMBL}
  4. +
  5. {@link android.view.MotionEvent#AXIS_Z}, +{@link android.view.MotionEvent#AXIS_RZ}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBR BUTTON_THUMBR}
  6. +
  7. {@link android.view.KeyEvent#KEYCODE_BUTTON_X BUTTON_X}
  8. +
  9. {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}
  10. +
  11. {@link android.view.KeyEvent#KEYCODE_BUTTON_Y BUTTON_Y}
  12. +
  13. {@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}
  14. +
  15. {@link android.view.KeyEvent#KEYCODE_BUTTON_R1 BUTTON_R1}
  16. +
  17. {@link android.view.MotionEvent#AXIS_RTRIGGER}, +{@link android.view.MotionEvent#AXIS_THROTTLE}
  18. +
  19. {@link android.view.MotionEvent#AXIS_LTRIGGER}, +{@link android.view.MotionEvent#AXIS_BRAKE}
  20. +
  21. {@link android.view.KeyEvent#KEYCODE_BUTTON_L1 BUTTON_L1}
  22. +
+
+

Common key codes generated by gamepad button presses include + {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}, +and {@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}. Some game +controllers also trigger the {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER +DPAD_CENTER} key code when the center of the D-pad crossbar is pressed. Your +game can inspect the key code by calling {@link android.view.KeyEvent#getKeyCode()} +or from key event callbacks such as +{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()}, +and if it represents an event that is relevant to your game, process it as a +game action. Table 1 lists the recommended game actions for the most common +gamepad buttons. +

+ +

+ Table 1. Recommended game actions for gamepad +buttons.

+ + + + + + + + + + + + + + + + + + + + + +
Game ActionButton Key Code
Start game in main menu, or pause/unpause during game{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}
Display menu{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT} and +{@link android.view.KeyEvent#KEYCODE_MENU}
Same as Android Back{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}* and +{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK}
Confirm selection, or perform primary game action{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}* and +{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER}
+

+* This could be the opposite button (A/B), depending on the locale that +you are supporting. +

+ +

Tip: Consider providing a configuration screen +in your game to allow users to personalize their own game controller mappings for +game actions.

+ +

The following snippet shows how you might override +{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()} to +associate the {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} and +{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER} button presses +with a game action. +

+
+public class GameView extends View {
+    ...
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        boolean handled = false;
+        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
+                == InputDevice.SOURCE_GAMEPAD) {
+            if (event.getRepeatCount() == 0) {
+                switch (keyCode) {
+                    // Handle gamepad and D-pad button presses to
+                    // navigate the ship
+                    ...
+
+                    default:
+                         if (isFireKey(keyCode)) {
+                             // Update the ship object to fire lasers
+                             ...
+                             handled = true;
+                         }
+                     break;
+                }
+            }
+            if (handled) {
+                return true;
+            }
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private static boolean isFireKey(int keyCode) {
+        // Here we treat Button_A and DPAD_CENTER as the primary action
+        // keys for the game. You may need to switch this to Button_B and
+        // DPAD_CENTER depending on the user expectations for the locale
+        // in which your game runs.
+        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
+    }
+}
+
+ +

Follow these best practices when handling button presses:

+ + +

Process Directional Pad Input

+

The 4-way directional pad (D-pad) is a common physical control in many game +controllers. Android reports D-pad UP and DOWN presses as +{@link android.view.MotionEvent#AXIS_HAT_Y} events with a range +from -1.0 (up) to 1.0 (down), and D-pad LEFT or RIGHT presses as +{@link android.view.MotionEvent#AXIS_HAT_X} events with a range from -1.0 +(left) to 1.0 (right).

+

Some controllers instead report D-pad presses with a key code. If your game +cares about D-pad presses, you should treat the hat axis events and the D-pad +key codes as the same input events, as recommended in table 2.

+

+ Table 2. Recommended default game actions for D-pad key + codes and hat axis values.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Game ActionD-pad Key CodeHat Axis Code
Move Up{@link android.view.KeyEvent#KEYCODE_DPAD_UP}{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to -1.0)
Move Down{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to 1.0)
Move Left{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to -1.0)
Move Right{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to 1.0)
+ + +

The following code snippet shows a helper class that lets you check the hat +axis and key code values from an input event to determine the D-pad direction. +

+
+public class Dpad {
+    final static int UP       = 0;
+    final static int LEFT     = 1;
+    final static int RIGHT    = 2;
+    final static int DOWN     = 3;
+    final static int CENTER   = 4;
+
+    int directionPressed = -1; // initialized to -1
+
+    public int getDirectionPressed(InputEvent event) {
+        if (!isDpadDevice(event)) {
+           return -1;
+        }
+
+        // If the input event is a MotionEvent, check its hat axis values.
+        if (event instanceof MotionEvent) {
+
+            // Use the hat axis value to find the D-pad direction
+            MotionEvent motionEvent = (MotionEvent) event;
+            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
+            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);
+
+            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
+            // LEFT and RIGHT direction accordingly.
+            if (Float.compare(xaxis, -1.0f) == 0) {
+                directionPressed =  Dpad.LEFT;
+            } else if (Float.compare(xaxis, 1.0f) == 0) {
+                directionPressed =  Dpad.RIGHT;
+            }
+            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
+            // UP and DOWN direction accordingly.
+            else if (Float.compare(yaxis, -1.0f) == 0) {
+                directionPressed =  Dpad.UP;
+            } else if (Float.compare(yaxis, -1.0f) == 0) {
+                directionPressed =  Dpad.DOWN;
+            }
+        }
+
+        // If the input event is a KeyEvent, check its key code.
+        else if (event instanceof KeyEvent) {
+
+           // Use the key code to find the D-pad direction.
+            KeyEvent keyEvent = (KeyEvent) event;
+            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
+                directionPressed = Dpad.LEFT;
+            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                directionPressed = Dpad.RIGHT;
+            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
+                directionPressed = Dpad.UP;
+            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
+                directionPressed = Dpad.DOWN;
+            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
+                directionPressed = Dpad.CENTER;
+            }
+        }
+        return directionPressed;
+    }
+
+    public static boolean isDpadDevice(InputEvent event) {
+        // Check that input comes from a device with directional pads.
+        if ((event.getSource() & InputDevice.SOURCE_DPAD)
+             != InputDevice.SOURCE_DPAD) {
+             return true;
+         } else {
+             return false;
+         }
+     }
+}
+
+ +

You can use this helper class in your game wherever you want to process + D-pad input (for example, in the +{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()} or +{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()} +callbacks).

+

For example:

+
+Dpad mDpad = new Dpad();
+...
+@Override
+public boolean onGenericMotionEvent(MotionEvent event) {
+
+    // Check if this event if from a D-pad and process accordingly.
+    if (Dpad.isDpadDevice(event)) {
+
+       int press = mDpad.getDirectionPressed(event);
+       switch (press) {
+            case LEFT:
+                // Do something for LEFT direction press
+                ...
+                return true;
+            case RIGHT:
+                // Do something for RIGHT direction press
+                ...
+                return true;
+            case UP:
+                // Do something for UP direction press
+                ...
+                return true;
+            ...
+        }
+    }
+
+    // Check if this event is from a joystick movement and process accordingly.
+    ...
+}
+
+ +

Process Joystick Movements

+

When players move a joystick on their game controllers, Android reports a +{@link android.view.MotionEvent} that contains the +{@link android.view.MotionEvent#ACTION_MOVE} action code and the updated +positions of the joystick's axes. Your game can use the data provided by +the {@link android.view.MotionEvent} to determine if a joystick movement it +cares about happened. +

+

Note that joystick motion events may batch multiple movement samples together +within a single object. The {@link android.view.MotionEvent} object contains +the current position for each joystick axis as well as multiple historical +positions for each axis. When reporting motion events with action code {@link android.view.MotionEvent#ACTION_MOVE} (such as joystick movements), Android batches up the +axis values for efficiency. The historical values for an axis consists of the +set of distinct values older than the current axis value, and more recent than +values reported in any previous motion events. See the +{@link android.view.MotionEvent} reference for details.

+

You can use the historical information to more accurately render a game +object's movement based on the joystick input. To +retrieve the current and historical values, call +{@link android.view.MotionEvent#getAxisValue(int) +getAxisValue()} or {@link android.view.MotionEvent#getHistoricalAxisValue(int, +int) getHistoricalAxisValue()}. You can also find the number of historical +points in the joystick event by calling +{@link android.view.MotionEvent#getHistorySize()}.

+

The following snippet shows how you might override the +{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()} callback to process joystick input. You should first +process the historical values for an axis, then process its current position. +

+
+public class GameView extends View {
+
+    @Override
+    public boolean onGenericMotionEvent(MotionEvent event) {
+
+        // Check that the event came from a game controller
+        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
+                InputDevice.SOURCE_JOYSTICK &&
+                event.getAction() == MotionEvent.ACTION_MOVE)
+
+            // Process all historical movement samples in the batch
+            final int historySize = event.getHistorySize();
+
+            // Process the movements starting from the
+            // earliest historical position in the batch
+            for (int i = 0; i < historySize; i++) {
+                // Process the event at historical position i
+                processJoystickInput(event, i);
+            }
+
+            // Process the current movement sample in the batch (position -1)
+            processJoystickInput(event, -1);
+            return true;
+        }
+        return super.onGenericMotionEvent(event);
+    }
+}
+
+

Before using joystick input, you need to determine if the joystick is +centered, then calculate its axis movements accordingly. Joysticks typically +have a flat area, that is, a range of values near the (0,0) coordinate +at which the axis is considered to be centered. If the axis value reported by +Android falls within the flat area, you should treat the controller to be at +rest (that is, motionless along both axes).

+

The snippet below shows a helper method that calculates the movement along +each axis. You invoke this helper in the {@code processJoystickInput()} method +described further below. +

+
+private static float getCenteredAxis(MotionEvent event,
+        InputDevice device, int axis, int historyPos) {
+    final InputDevice.MotionRange range =
+            device.getMotionRange(axis, event.getSource());
+
+    // A joystick at rest does not always report an absolute position of
+    // (0,0). Use the getFlat() method to determine the range of values
+    // bounding the joystick axis center.
+    if (range != null) {
+        final float flat = range.getFlat();
+        final float value =
+                historyPos < 0 ? event.getAxisValue(axis):
+                event.getHistoricalAxisValue(axis, historyPos);
+
+        // Ignore axis values that are within the 'flat' region of the
+        // joystick axis center.
+        if (Math.abs(value) > flat) {
+            return value;
+        }
+    }
+    return 0;
+}
+
+

Putting it all together, here is how you might process joystick movements in +your game:

+
+private void processJoystickInput(MotionEvent event,
+        int historyPos) {
+
+    InputDevice mInputDevice = event.getDevice();
+
+    // Calculate the horizontal distance to move by
+    // using the input value from one of these physical controls:
+    // the left control stick, hat axis, or the right control stick.
+    float x = getCenteredAxis(event, mInputDevice,
+            MotionEvent.AXIS_X, historyPos);
+    if (x == 0) {
+        x = getCenteredAxis(event, mInputDevice,
+                MotionEvent.AXIS_HAT_X, historyPos);
+    }
+    if (x == 0) {
+        x = getCenteredAxis(event, mInputDevice,
+                MotionEvent.AXIS_Z, historyPos);
+    }
+
+    // Calculate the vertical distance to move by
+    // using the input value from one of these physical controls:
+    // the left control stick, hat switch, or the right control stick.
+    float y = getCenteredAxis(event, mInputDevice,
+            MotionEvent.AXIS_Y, historyPos);
+    if (y == 0) {
+        y = getCenteredAxis(event, mInputDevice,
+                MotionEvent.AXIS_HAT_Y, historyPos);
+    }
+    if (y == 0) {
+        y = getCenteredAxis(event, mInputDevice,
+                MotionEvent.AXIS_RZ, historyPos);
+    }
+
+    // Update the ship object based on the new x and y values
+    ...
+
+    return true;
+}
+
+

To support game controllers that have more sophisticated +features beyond a single joystick, follow these best practices:

+ \ No newline at end of file diff --git a/docs/html/training/game-controllers/index.jd b/docs/html/training/game-controllers/index.jd new file mode 100644 index 000000000000..29764204bca1 --- /dev/null +++ b/docs/html/training/game-controllers/index.jd @@ -0,0 +1,60 @@ +page.title=Supporting Game Controllers +page.tags="game controller" + +trainingnavtop=true +startpage=true + +@jd:body + +
+
+ + +

Dependencies and prerequisites

+
    +
  • Android 2.3 (API level 9) or higher.
  • +
+ +

You should also read

+ + +

Try it out

+
+ Download the sample +

ControllerSample.zip

+
+ +
+
+ +

You can greatly enhance the user experience in your game by letting +players use their favorite game controllers. The Android framework provides + APIs for detecting and processing user input from game controllers.

+ +

This class shows how to make your game work consistently with game +controllers across different Android API levels (API level 9 and up), +and how to enhance the gaming experience for players by supporting multiple +controllers simultaneously in your app.

+ +

Lessons

+ +
+
Handling Controller +Actions
+
Learn how to handle user input from common +input elements on game controllers, including directional pad (D-pad) +buttons, gamepad buttons, and joysticks.
+
Supporting Controllers Across Android +Versions
+
Learn how to make game controllers behave the same across +devices running different versions of Android.
+
Supporting Multiple Game +Controllers
+
Learn how to detect and use multiple game controllers that +are simultaneously connected.
diff --git a/docs/html/training/game-controllers/multiple-controllers.jd b/docs/html/training/game-controllers/multiple-controllers.jd new file mode 100644 index 000000000000..6fcad45cbe1a --- /dev/null +++ b/docs/html/training/game-controllers/multiple-controllers.jd @@ -0,0 +1,130 @@ +page.title=Supporting Multiple Game Controllers +trainingnavtop=true + +@jd:body + + +
+
+ +

This lesson teaches you to

+
    +
  1. Map Players to Controller Device IDs
  2. +
  3. Process Multiple Controller Input
  4. +
+ +

Try it out

+
+ Download the sample +

ControllerSample.zip

+
+ + +
+
+

While most games are designed to support a single user per Android device, +it's also possible to support multiple users with game controllers that are +connected simultaneously on the same Android device.

+

This lesson covers some basic techniques for handling input in your single +device multiplayer game from multiple connected controllers. This includes +maintaining a mapping between player avatars and each controller device and +processing controller input events appropriately. +

+ +

Map Players to Controller Device IDs

+

When a game controller is connected to an Android device, the system +assigns it an integer device ID. You can obtain the device IDs for connected +game controllers by calling {@link android.view.InputDevice#getDeviceIds() InputDevice.getDeviceIds()}, as shown in Verify a Game Controller is Connected. You can then associate each +device ID with a player in your game, and process game actions for each player separately. +

+

Note: On devices running Android 4.1 (API +level 16) and higher, you can obtain an input device’s descriptor using +{@link android.view.InputDevice#getDescriptor()}, which returns a unique +persistent string value for the input device. Unlike a device ID, the descriptor +value won't change even if the input device is disconnected, reconnected, or +reconfigured. +

+

The code snippet below shows how to use a {@link android.util.SparseArray} +to associate a player's avatar with a specific controller. In this example, the +{@code mShips} variable stores a collection of {@code Ship} objects. A new +player avatar is created in-game when a new controller is attached by a user, +and removed when its associated controller is removed. +

+

The {@code onInputDeviceAdded()} and {@code onInputDeviceRemoved()} callback +methods are part of the abstraction layer introduced in + +Supporting Controllers Across Android Versions. By implementing these +listener callbacks, your game can identify the game controller's device ID when a +controller is added or removed. This detection is compatible with Android 2.3 +(API level 9) and higher. +

+ +
+private final SparseArray<Ship> mShips = new SparseArray<Ship>();
+
+@Override
+public void onInputDeviceAdded(int deviceId) {
+    getShipForID(deviceId);
+}
+
+@Override
+public void onInputDeviceRemoved(int deviceId) {
+    removeShipForID(deviceId);
+}
+
+private Ship getShipForID(int shipID) {
+    Ship currentShip = mShips.get(shipID);
+    if ( null == currentShip ) {
+        currentShip = new Ship();
+        mShips.append(shipID, currentShip);
+    }
+    return currentShip;
+}
+
+private void removeShipForID(int shipID) {
+    mShips.remove(shipID);
+}
+
+ +

Process Multiple Controller Input

+

Your game should execute the following loop to process +input from multiple controllers: +

+
    +
  1. Detect whether an input event occurred.
  2. +
  3. Identify the input source and its device ID.
  4. +
  5. Based on the action indicated by the input event key code or axis value, + update the player avatar associated with that device ID.
  6. +
  7. Render and update the user interface.
  8. +
+

{@link android.view.KeyEvent} and {@link android.view.MotionEvent} input +events have device IDs associated with them. Your game can take advantage of +this to determine which controller the input event came from, and update the +player avatar associated with that controller. +

+

The following code snippet shows how you might get a player avatar reference +corresponding to a game controller device ID, and update the game based on the +user's button press on that controller. +

+
+@Override
+public boolean onKeyDown(int keyCode, KeyEvent event) {
+    if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
+                == InputDevice.SOURCE_GAMEPAD) {
+        int deviceId = event.getDeviceId();
+        if (deviceId != -1) {
+            Ship currentShip = getShipForId(deviceId);
+            // Based on which key was pressed, update the player avatar
+            // (e.g. set the ship headings or fire lasers)
+            ...
+            return true;
+        }
+    }
+    return super.onKeyDown(keyCode, event);
+}
+
+

Note: As a best practice, when a user's +game controller disconnects, you should pause the game and ask if the user +wants to reconnect. +

diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 1314c7aad363..972faf4f6b56 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -1,6 +1,4 @@ + + diff --git a/docs/image_sources/training/game-controllers/backward-compatible-inputmanager.graffle b/docs/image_sources/training/game-controllers/backward-compatible-inputmanager.graffle new file mode 100644 index 000000000000..fbf78be964e0 --- /dev/null +++ b/docs/image_sources/training/game-controllers/backward-compatible-inputmanager.graffle @@ -0,0 +1,1076 @@ + + + + + ActiveLayerIndex + 0 + ApplicationVersion + + com.omnigroup.OmniGrafflePro + 139.18.0.187838 + + AutoAdjust + + BackgroundGraphic + + Bounds + {{0, 0}, {756, 553}} + Class + SolidGraphic + ID + 2 + Style + + shadow + + Draws + NO + + stroke + + Draws + NO + + + + BaseZoom + 0 + CanvasOrigin + {0, 0} + ColumnAlign + 1 + ColumnSpacing + 36 + CreationDate + 2013-10-03 20:37:16 +0000 + Creator + Quddus Chong + DisplayScale + 1 0/72 in = 1.0000 in + GraphDocumentVersion + 8 + GraphicsList + + + Bounds + {{69.703689575195312, 209}, {115.59260559082031, 42.580599999999997}} + Class + ShapedGraphic + FontInfo + + Color + + a + 0.5 + b + 0 + g + 0 + r + 0 + + Font + Helvetica + Size + 12 + + ID + 205 + Shape + Rectangle + Style + + fill + + Draws + NO + + shadow + + Draws + NO + + stroke + + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 Your game code} + + + + Class + LineGraphic + Head + + ID + 157 + Info + 8 + + ID + 204 + Points + + {212, 269.08485000000002} + {283.00000000000006, 269.08485000000002} + + Style + + stroke + + HeadArrow + FilledArrow + Legacy + + TailArrow + 0 + + + Tail + + ID + 203 + Info + 7 + + + + Bounds + {{43, 244}, {169, 50.169699999999999}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0 + g + 0 + r + 0 + + Font + DroidSans-Bold + Size + 10 + + ID + 203 + Magnets + + {1, 1} + {1, -1} + {-1, -1} + {-1, 1} + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + {-0.5, -0.233518} + {-0.49144199999999999, 0.26006299999999999} + {0.50711799999999996, -0.22408600000000001} + {0.50711799999999996, 0.267179} + {-0.27431, -0.474028} + {0.27977999999999997, -0.47847800000000001} + {0.29393799999999998, 0.54304399999999997} + {-0.28623199999999999, 0.55380399999999996} + + Shape + Rectangle + Style + + fill + + Color + + b + 0 + g + 0.5 + r + 1 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 0.2 + g + 0.4 + r + 0.6 + + + shadow + + Color + + a + 0.35 + b + 0 + g + 0 + r + 0 + + Fuzziness + 2.3972222805023193 + ShadowVector + {0, 1} + + stroke + + Color + + b + 0.2 + g + 0.4 + r + 0.6 + + CornerRadius + 3 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\b\fs24 \cf0 GameView} + VerticalPad + 0 + + + + Bounds + {{519.5, 399}, {162, 42.580599999999997}} + Class + ShapedGraphic + FontInfo + + Color + + a + 0.5 + b + 0 + g + 0 + r + 0 + + Font + Helvetica + Size + 12 + + ID + 202 + Shape + Rectangle + Style + + fill + + Draws + NO + + shadow + + Draws + NO + + stroke + + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 Implementation on Android 2.3 up to Android 4.0} + + + + Bounds + {{526.5, 108.4194}, {148, 42.580599999999997}} + Class + ShapedGraphic + FontInfo + + Color + + a + 0.5 + b + 0 + g + 0 + r + 0 + + Font + Helvetica + Size + 12 + + ID + 201 + Shape + Rectangle + Style + + fill + + Draws + NO + + shadow + + Draws + NO + + stroke + + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 "Proxy" implementation on Android 4.1 and higher} + + + + Bounds + {{327.40739440917969, 209}, {80.185199999999995, 42.580599999999997}} + Class + ShapedGraphic + FontInfo + + Color + + a + 0.5 + b + 0 + g + 0 + r + 0 + + Font + Helvetica + Size + 12 + + ID + 100 + Shape + Rectangle + Style + + fill + + Draws + NO + + shadow + + Draws + NO + + stroke + + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 Interface} + + + + Class + LineGraphic + FontInfo + + Font + DroidSans + Size + 11 + + Head + + ID + 157 + Info + 7 + + ID + 200 + OrthogonalBarAutomatic + + OrthogonalBarPoint + {0, 0} + OrthogonalBarPosition + 20.600000381469727 + Points + + {540, 368.91515000000004} + {475, 283} + {451.99999999999994, 269.08485000000002} + + Style + + stroke + + Color + + a + 0.7 + b + 0 + g + 0 + r + 0 + + CornerRadius + 4 + HeadArrow + FilledArrow + Legacy + + LineType + 2 + TailArrow + FilledArrow + + + Tail + + ID + 159 + Info + 8 + + + + Class + LineGraphic + FontInfo + + Font + DroidSans + Size + 11 + + Head + + ID + 157 + Info + 7 + + ID + 198 + OrthogonalBarAutomatic + + OrthogonalBarPoint + {0, 0} + OrthogonalBarPosition + 20.657249450683594 + Points + + {540, 176.08484999999999} + {474.88549999999998, 177.5} + {451.99999999999994, 269.08485000000002} + + Style + + stroke + + Color + + a + 0.7 + b + 0 + g + 0 + r + 0 + + CornerRadius + 4 + HeadArrow + FilledArrow + Legacy + + LineType + 2 + TailArrow + FilledArrow + + + Tail + + ID + 158 + Info + 8 + + + + Bounds + {{540, 343.83030000000002}, {121, 50.169699999999999}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0 + g + 0 + r + 0 + + Font + DroidSans-Bold + Size + 10 + + ID + 159 + Magnets + + {1, 1} + {1, -1} + {-1, -1} + {-1, 1} + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + {-0.5, -0.233518} + {-0.49144199999999999, 0.26006299999999999} + {0.50711799999999996, -0.22408600000000001} + {0.50711799999999996, 0.267179} + {-0.27431, -0.474028} + {0.27977999999999997, -0.47847800000000001} + {0.29393799999999998, 0.54304399999999997} + {-0.28623199999999999, 0.55380399999999996} + + Shape + Rectangle + Style + + fill + + Color + + b + 1 + g + 0.874135 + r + 0.71718 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 1 + g + 0.662438 + r + 0.464468 + + + shadow + + Color + + a + 0.35 + b + 0 + g + 0 + r + 0 + + Fuzziness + 2.3972222805023193 + ShadowVector + {0, 1} + + stroke + + Color + + b + 0.93512 + g + 0.472602 + r + 0.333854 + + CornerRadius + 3 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\b\fs24 \cf0 InputManagerV9} + VerticalPad + 0 + + + + Bounds + {{540, 151}, {121, 50.169699999999999}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0 + g + 0 + r + 0 + + Font + DroidSans-Bold + Size + 10 + + ID + 158 + Magnets + + {1, 1} + {1, -1} + {-1, -1} + {-1, 1} + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + {-0.5, -0.233518} + {-0.49144199999999999, 0.26006299999999999} + {0.50711799999999996, -0.22408600000000001} + {0.50711799999999996, 0.267179} + {-0.27431, -0.474028} + {0.27977999999999997, -0.47847800000000001} + {0.29393799999999998, 0.54304399999999997} + {-0.28623199999999999, 0.55380399999999996} + + Shape + Rectangle + Style + + fill + + Color + + b + 1 + g + 0.874135 + r + 0.71718 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 1 + g + 0.662438 + r + 0.464468 + + + shadow + + Color + + a + 0.35 + b + 0 + g + 0 + r + 0 + + Fuzziness + 2.3972222805023193 + ShadowVector + {0, 1} + + stroke + + Color + + b + 0.93512 + g + 0.472602 + r + 0.333854 + + CornerRadius + 3 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\b\fs24 \cf0 InputManagerV16} + VerticalPad + 0 + + + + Bounds + {{283, 244}, {169, 50.169699999999999}} + Class + ShapedGraphic + FontInfo + + Color + + b + 0 + g + 0 + r + 0 + + Font + DroidSans-Bold + Size + 10 + + ID + 157 + Magnets + + {1, 1} + {1, -1} + {-1, -1} + {-1, 1} + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + {-0.5, -0.233518} + {-0.49144199999999999, 0.26006299999999999} + {0.50711799999999996, -0.22408600000000001} + {0.50711799999999996, 0.267179} + {-0.27431, -0.474028} + {0.27977999999999997, -0.47847800000000001} + {0.29393799999999998, 0.54304399999999997} + {-0.28623199999999999, 0.55380399999999996} + + Shape + Rectangle + Style + + fill + + Color + + b + 1 + g + 0.874135 + r + 0.71718 + + FillType + 2 + GradientAngle + 90 + GradientColor + + b + 1 + g + 0.662438 + r + 0.464468 + + + shadow + + Color + + a + 0.35 + b + 0 + g + 0 + r + 0 + + Fuzziness + 2.3972222805023193 + ShadowVector + {0, 1} + + stroke + + Color + + b + 0.93512 + g + 0.472602 + r + 0.333854 + + CornerRadius + 3 + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\b\fs24 \cf0 InputManagerCompat} + VerticalPad + 0 + + + + GridInfo + + GuidesLocked + NO + GuidesVisible + YES + HPages + 1 + ImageCounter + 1 + KeepToScale + + Layers + + + Lock + NO + Name + Layer 1 + Print + YES + View + YES + + + LayoutInfo + + Animate + NO + circoMinDist + 18 + circoSeparation + 0.0 + layoutEngine + dot + neatoSeparation + 0.0 + twopiSeparation + 0.0 + + LinksVisible + NO + MagnetsVisible + NO + MasterSheets + + ModificationDate + 2014-01-17 19:18:34 +0000 + Modifier + Quddus Chong + NotesVisible + NO + Orientation + 2 + OriginVisible + NO + PageBreaks + YES + PrintInfo + + NSBottomMargin + + float + 41 + + NSHorizonalPagination + + coded + BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG + + NSLeftMargin + + float + 18 + + NSOrientation + + coded + BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwGG + + NSPaperSize + + size + {792, 612} + + NSPrintReverseOrientation + + int + 0 + + NSRightMargin + + float + 18 + + NSTopMargin + + float + 18 + + + PrintOnePage + + ReadOnly + NO + RowAlign + 1 + RowSpacing + 36 + SheetTitle + Canvas 1 + SmartAlignmentGuidesActive + YES + SmartDistanceGuidesActive + YES + UniqueID + 1 + UseEntirePage + + VPages + 1 + WindowInfo + + CurrentSheet + 0 + ExpandedCanvases + + + name + Canvas 1 + + + Frame + {{130, 0}, {1112, 878}} + ListView + + OutlineWidth + 142 + RightSidebar + + ShowRuler + + Sidebar + + SidebarWidth + 120 + VisibleRegion + {{-111, -85}, {977, 723}} + Zoom + 1 + ZoomValues + + + Canvas 1 + 1 + 1 + + + + + diff --git a/docs/image_sources/training/game-controllers/game-controller-profiles.graffle b/docs/image_sources/training/game-controllers/game-controller-profiles.graffle new file mode 100644 index 000000000000..fc62fc595c8f --- /dev/null +++ b/docs/image_sources/training/game-controllers/game-controller-profiles.graffle @@ -0,0 +1,2773 @@ + + + + + ActiveLayerIndex + 1 + ApplicationVersion + + com.omnigroup.OmniGrafflePro + 139.18.0.187838 + + AutoAdjust + + BackgroundGraphic + + Bounds + {{0, 0}, {1152, 733}} + Class + SolidGraphic + ID + 2 + Style + + shadow + + Draws + NO + + stroke + + Draws + NO + + + + BaseZoom + 0 + CanvasOrigin + {0, 0} + ColumnAlign + 1 + ColumnSpacing + 36 + CreationDate + 2013-03-04 20:35:59 +0000 + Creator + Dan Galpin + DisplayScale + 1 0/72 in = 1.0000 in + ExportShapes + + + InspectorGroup + 255 + ShapeImageRect + {{2, 2}, {22, 22}} + ShapeName + F1E2180B-1353-44D7-847D-FBDA9B734CEF-643-000123C8E8BEFFA9 + ShouldExport + YES + StrokePath + + elements + + + element + MOVETO + point + {0.5, 0.49999619000000001} + + + control1 + {0.48931121999999999, 0.25319671999999999} + control2 + {0.44103335999999999, 0.012794494999999999} + element + CURVETO + point + {0.35516356999999998, -0.17620087000000001} + + + control1 + {0.15901183999999999, -0.60793686000000002} + control2 + {-0.15901278999999999, -0.60793686000000002} + element + CURVETO + point + {-0.35516452999999998, -0.17620087000000001} + + + control1 + {-0.44103335999999999, 0.012794494999999999} + control2 + {-0.48931216999999999, 0.25319671999999999} + element + CURVETO + point + {-0.5, 0.49999619000000001} + + + element + CLOSE + + + element + MOVETO + point + {0.5, 0.49999619000000001} + + + + TextBounds + {{0, 0}, {1, 1}} + + + InspectorGroup + 255 + ShapeImageRect + {{2, 2}, {22, 22}} + ShapeName + 3183FE08-E827-4DFB-B09C-26BEBDBFE644-643-00011FC72A7BAF86 + ShouldExport + YES + StrokePath + + elements + + + element + MOVETO + point + {0.16853929000000001, -0.16853952} + + + element + LINETO + point + {0.16853929000000001, -0.5} + + + element + LINETO + point + {-0.16853940000000001, -0.5} + + + element + LINETO + point + {-0.16853940000000001, -0.16853952} + + + element + LINETO + point + {-0.5, -0.16853952} + + + element + LINETO + point + {-0.5, 0.16853929000000001} + + + element + LINETO + point + {-0.16853940000000001, 0.16853929000000001} + + + element + LINETO + point + {-0.16853940000000001, 0.5} + + + element + LINETO + point + {0.16853929000000001, 0.5} + + + element + LINETO + point + {0.16853929000000001, 0.16853929000000001} + + + element + LINETO + point + {0.5, 0.16853929000000001} + + + element + LINETO + point + {0.5, -0.16853952} + + + element + CLOSE + + + element + MOVETO + point + {0.16853929000000001, -0.16853952} + + + + TextBounds + {{0, 0}, {1, 1}} + + + InspectorGroup + 255 + ShapeImageRect + {{2, 2}, {22, 22}} + ShapeName + C23360B7-896D-4E43-821E-E438291A4B42-643-000123B31059F14D + ShouldExport + YES + StrokePath + + elements + + + element + MOVETO + point + {0.12745094000000001, 0} + + + element + LINETO + point + {0.5, 0} + + + element + LINETO + point + {0.5, -0.5} + + + element + LINETO + point + {-0.5, -0.5} + + + element + LINETO + point + {-0.5, 0} + + + element + LINETO + point + {-0.12745094000000001, 0} + + + element + LINETO + point + {-0.12745094000000001, 0.5} + + + element + LINETO + point + {0.12745094000000001, 0.5} + + + element + CLOSE + + + element + MOVETO + point + {0.12745094000000001, 0} + + + + TextBounds + {{0, 0}, {1, 1}} + + + InspectorGroup + 255 + ShapeImageRect + {{2, 2}, {22, 22}} + ShapeName + 47D48890-EFFB-40D4-9B16-B837B487F35B-643-000123949D248202 + ShouldExport + YES + StrokePath + + elements + + + element + MOVETO + point + {-0.45872511999999999, -0.5} + + + control1 + {-0.51545845999999995, -0.26532650000000002} + control2 + {-0.51370585000000002, 0.098152161000000002} + element + CURVETO + point + {-0.45346713, 0.32486773000000002} + + + control1 + {-0.39142346, 0.55837822000000004} + control2 + {-0.29083102999999999, 0.55837822000000004} + element + CURVETO + point + {-0.22878745, 0.32486773000000002} + + + control1 + {-0.22439509999999999, 0.30833673} + control2 + {-0.2203137, 0.29107856999999998} + element + CURVETO + point + {-0.21654329, 0.27319621999999999} + + + element + LINETO + point + {0, 0.27319621999999999} + + + element + LINETO + point + {0.21654325999999999, 0.27319621999999999} + + + control1 + {0.22031379000000001, 0.29107856999999998} + control2 + {0.22439516000000001, 0.30833673} + element + CURVETO + point + {0.22878741999999999, 0.32486773000000002} + + + control1 + {0.29083102999999999, 0.55837822000000004} + control2 + {0.39142357999999999, 0.55837822000000004} + element + CURVETO + point + {0.45346701, 0.32486773000000002} + + + control1 + {0.51370572999999997, 0.098152161000000002} + control2 + {0.51545845999999995, -0.26532650000000002} + element + CURVETO + point + {0.45872509, -0.5} + + + element + LINETO + point + {0.22352933999999999, -0.5} + + + control1 + {0.22344869000000001, -0.49966621} + control2 + {0.22336811000000001, -0.49933242999999999} + element + CURVETO + point + {0.22328770000000001, -0.49899817000000002} + + + element + LINETO + point + {0, -0.49899817000000002} + + + element + LINETO + point + {-0.22328772999999999, -0.49899817000000002} + + + control1 + {-0.22336813999999999, -0.49933242999999999} + control2 + {-0.22344869000000001, -0.49966621} + element + CURVETO + point + {-0.22352933999999999, -0.5} + + + element + CLOSE + + + element + MOVETO + point + {-0.45872511999999999, -0.5} + + + + TextBounds + {{0, 0}, {1, 1}} + + + InspectorGroup + 255 + ShapeImageRect + {{2, 2}, {22, 22}} + ShapeName + 1ECB1682-27C7-4AA3-B9D7-C873C7E35CC3-643-0001204E86C9927B + ShouldExport + YES + StrokePath + + elements + + + element + MOVETO + point + {0, -0.34815770000000001} + + + element + LINETO + point + {-0.17229146000000001, -0.34815770000000001} + + + control1 + {-0.17213887, -0.38709176000000001} + control2 + {-0.17783916, -0.43129521999999998} + element + CURVETO + point + {-0.19134480000000001, -0.45584047} + + + control1 + {-0.20903580999999999, -0.48799281999999999} + control2 + {-0.30287585, -0.52989668000000001} + element + CURVETO + point + {-0.36076200000000003, -0.46868926} + + + control1 + {-0.40301216000000001, -0.42401546000000001} + control2 + {-0.4273439, -0.36313765999999997} + element + CURVETO + point + {-0.44148481000000001, -0.33348655999999999} + + + control1 + {-0.45792991, -0.29900670000000001} + control2 + {-0.47685239000000001, -0.18920386} + element + CURVETO + point + {-0.47935599000000001, -0.17634570999999999} + + + control1 + {-0.50688135999999995, -0.035002232000000001} + control2 + {-0.50688135999999995, 0.19415568999999999} + element + CURVETO + point + {-0.47935599000000001, 0.33549797999999997} + + + control1 + {-0.47144359000000002, 0.37612997999999997} + control2 + {-0.46211770000000002, 0.40508091000000002} + element + CURVETO + point + {-0.45219076000000002, 0.42235231000000001} + + + control1 + {-0.45041117000000003, 0.42638290000000001} + control2 + {-0.44854629000000001, 0.43031728000000002} + element + CURVETO + point + {-0.44659655999999998, 0.43414568999999997} + + + control1 + {-0.40189775999999999, 0.52195132} + control2 + {-0.32942747999999999, 0.52195132} + element + CURVETO + point + {-0.28472847000000001, 0.43414568999999997} + + + control1 + {-0.252276, 0.37039875999999999} + control2 + {-0.24518039999999999, 0.31812059999999998} + element + CURVETO + point + {-0.24518039999999999, 0.31812059999999998} + + + control1 + {-0.23554211999999999, 0.26876652000000001} + control2 + {-0.23579177000000001, 0.25717378000000002} + element + CURVETO + point + {-0.22040003999999999, 0.28052354000000002} + + + control1 + {-0.17852514999999999, 0.34405743999999999} + control2 + {-0.11063104999999999, 0.34405743999999999} + element + CURVETO + point + {-0.068755567000000004, 0.28052354000000002} + + + control1 + {-0.053364097999999999, 0.25717378000000002} + control2 + {-0.043629705999999997, 0.22848975999999999} + element + CURVETO + point + {-0.039552628999999999, 0.19839119999999999} + + + element + LINETO + point + {0, 0.19839119999999999} + + + element + LINETO + point + {0.039552628999999999, 0.19839119999999999} + + + control1 + {0.043629705999999997, 0.22848975999999999} + control2 + {0.053364097999999999, 0.25717378000000002} + element + CURVETO + point + {0.068755567000000004, 0.28052354000000002} + + + control1 + {0.11063104999999999, 0.34405743999999999} + control2 + {0.17852509, 0.34405743999999999} + element + CURVETO + point + {0.22040008999999999, 0.28052354000000002} + + + control1 + {0.23579174, 0.25717378000000002} + control2 + {0.23554211999999999, 0.26876652000000001} + element + CURVETO + point + {0.24518037000000001, 0.31812059999999998} + + + control1 + {0.24518037000000001, 0.31812059999999998} + control2 + {0.252276, 0.37039875999999999} + element + CURVETO + point + {0.28472847000000001, 0.43414568999999997} + + + control1 + {0.32942747999999999, 0.52195132} + control2 + {0.40189778999999998, 0.52195132} + element + CURVETO + point + {0.44659662, 0.43414568999999997} + + + control1 + {0.44854629000000001, 0.43031728000000002} + control2 + {0.45041120000000001, 0.42638290000000001} + element + CURVETO + point + {0.45219076000000002, 0.42235231000000001} + + + control1 + {0.46211766999999998, 0.40508091000000002} + control2 + {0.47144364999999999, 0.37612997999999997} + element + CURVETO + point + {0.47935592999999999, 0.33549797999999997} + + + control1 + {0.50688135999999995, 0.19415568999999999} + control2 + {0.50688135999999995, -0.035002232000000001} + element + CURVETO + point + {0.47935592999999999, -0.17634570999999999} + + + control1 + {0.47685242, -0.18920386} + control2 + {0.45792985000000003, -0.29900670000000001} + element + CURVETO + point + {0.44148481000000001, -0.33348655999999999} + + + control1 + {0.42734396000000002, -0.36313765999999997} + control2 + {0.40301216000000001, -0.42401546000000001} + element + CURVETO + point + {0.36076200000000003, -0.46868926} + + + control1 + {0.30287587999999999, -0.52989668000000001} + control2 + {0.20903580999999999, -0.48799281999999999} + element + CURVETO + point + {0.19134480000000001, -0.45584047} + + + control1 + {0.17783916, -0.43129521999999998} + control2 + {0.17213881, -0.38709176000000001} + element + CURVETO + point + {0.17229140000000001, -0.34815770000000001} + + + element + CLOSE + + + element + MOVETO + point + {0, -0.34815770000000001} + + + + TextBounds + {{0, 0}, {1, 1}} + + + InspectorGroup + 255 + ShapeImageRect + {{2, 2}, {22, 22}} + ShapeName + A5792564-7BB8-4CC5-9068-338E366F4CDF-643-000123FA94AC0471 + ShouldExport + YES + StrokePath + + elements + + + element + MOVETO + point + {0.49999988000000001, 0.5} + + + element + LINETO + point + {0.49999988000000001, -0.49681281999999999} + + + element + LINETO + point + {0.41531157000000002, -0.49681281999999999} + + + control1 + {0.41504394999999999, -0.49787712000000001} + control2 + {0.41477596999999999, -0.49893760999999998} + element + CURVETO + point + {0.41450751000000002, -0.5} + + + element + LINETO + point + {-0.36829965999999997, -0.5} + + + control1 + {-0.44126809, -0.21159172000000001} + control2 + {-0.48516821999999998, 0.13798714000000001} + element + CURVETO + point + {-0.50000005999999997, 0.5} + + + element + CLOSE + + + element + MOVETO + point + {0.49999988000000001, 0.5} + + + + TextBounds + {{0, 0}, {1, 1}} + + + GraphDocumentVersion + 8 + GraphicsList + + + Class + Group + Graphics + + + Bounds + {{352.69015502929688, 157.75728808270409}, {39.000000000000007, 26.271913528442393}} + Class + ShapedGraphic + HFlip + YES + ID + 266 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 4} + + VFlip + YES + + + Bounds + {{368.23110857347933, 183.90486894506589}, {12.74318471799012, 11.874331160357771}} + Class + ShapedGraphic + HFlip + YES + ID + 267 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + VFlip + YES + + + HFlip + YES + ID + 265 + Layer + 1 + VFlip + YES + + + Class + Group + Graphics + + + Bounds + {{391.69015389164184, 123.94366571766557}, {39.000000000000007, 26.271913528442393}} + Class + ShapedGraphic + HFlip + YES + ID + 263 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 6} + + VFlip + YES + + + Bounds + {{407.2311074358243, 150.09124658002736}, {12.74318471799012, 11.874331160357771}} + Class + ShapedGraphic + HFlip + YES + ID + 264 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + VFlip + YES + + + HFlip + YES + ID + 262 + Layer + 1 + VFlip + YES + + + Class + Group + Graphics + + + Bounds + {{450.8726169276494, 218.13094531464776}, {39.000000000000007, 26.271913528442397}} + Class + ShapedGraphic + ID + 260 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 7} + + + + Bounds + {{461.58847866547882, 206.38094682037035}, {12.743184717990125, 11.874331160357764}} + Class + ShapedGraphic + ID + 261 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + + + ID + 259 + Layer + 1 + + + Class + Group + Graphics + + + Bounds + {{408.19367815071467, 250.48240007546275}, {39.000000000000007, 26.271913528442397}} + Class + ShapedGraphic + ID + 257 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 5} + + + + Bounds + {{418.90953988854409, 238.73240158118534}, {12.743184717990125, 11.874331160357764}} + Class + ShapedGraphic + ID + 258 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + + + ID + 256 + Layer + 1 + + + Class + Group + Graphics + + + Bounds + {{177.5007049887455, 155.402868593243}, {39, 26.271913528442383}} + Class + ShapedGraphic + ID + 248 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 1} + + VFlip + YES + + + Bounds + {{188.21656672657295, 181.55044945560479}, {12.743184717990118, 11.874331160357769}} + Class + ShapedGraphic + ID + 249 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + VFlip + YES + + + ID + 247 + Layer + 1 + VFlip + YES + + + Class + Group + Graphics + + + Bounds + {{1033.9784545898438, 270.88415416934021}, {39.000000000000007, 26.271913528442397}} + Class + ShapedGraphic + ID + 245 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 11} + + + + Bounds + {{1044.6943163276731, 259.1341556750628}, {12.743184717990125, 11.874331160357764}} + Class + ShapedGraphic + ID + 246 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + + + ID + 244 + Layer + 1 + + + Class + Group + Graphics + + + Bounds + {{963.48967113392712, 314.58124191062183}, {39.000000000000021, 26.271913528442411}} + Class + ShapedGraphic + HFlip + YES + ID + 242 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 10} + + + + Bounds + {{979.03062467810946, 302.83124341634465}, {12.743184717990129, 11.874331160357775}} + Class + ShapedGraphic + HFlip + YES + ID + 243 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + + + HFlip + YES + ID + 241 + Layer + 1 + + + Class + Group + Graphics + + + Bounds + {{692.25354204809798, 314.58123668887146}, {39.000000000000007, 26.271913528442397}} + Class + ShapedGraphic + ID + 53 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 9} + + + + Bounds + {{702.9694037859274, 302.83123819459405}, {12.743184717990125, 11.874331160357764}} + Class + ShapedGraphic + ID + 54 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + + + ID + 52 + Layer + 1 + + + Class + Group + Graphics + + + Bounds + {{629.80290222167969, 270.8841541693406}, {39.000000000000021, 26.271913528442411}} + Class + ShapedGraphic + HFlip + YES + ID + 233 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 8} + + + + Bounds + {{645.34385576586203, 259.13415567506343}, {12.743184717990129, 11.874331160357775}} + Class + ShapedGraphic + HFlip + YES + ID + 234 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + + + HFlip + YES + ID + 232 + Layer + 1 + + + Class + Group + Graphics + + + Bounds + {{307.48943270607771, 230.98592247383127}, {39.000000000000007, 26.271913528442393}} + Class + ShapedGraphic + HFlip + YES + ID + 224 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 3} + + VFlip + YES + + + Bounds + {{323.03038625026016, 257.13350333619303}, {12.74318471799012, 11.874331160357771}} + Class + ShapedGraphic + HFlip + YES + ID + 225 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + VFlip + YES + + + HFlip + YES + ID + 223 + Layer + 1 + VFlip + YES + + + Class + Group + Graphics + + + Bounds + {{219.01409112610222, 230.98592247383127}, {39, 26.271913528442383}} + Class + ShapedGraphic + ID + 44 + Shape + Rectangle + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + CornerRadius + 4 + Draws + NO + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf1 2} + + VFlip + YES + + + Bounds + {{229.7299528639297, 257.13350333619303}, {12.743184717990118, 11.874331160357769}} + Class + ShapedGraphic + ID + 45 + Shape + Bezier + ShapeData + + UnitPoints + + {0.5, 0.5} + {0.5, 0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, -0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {-0.5, 0.5} + {0.5, 0.5} + + + Style + + fill + + Color + + b + 0.898039 + g + 0.709804 + r + 0.2 + + + shadow + + Fuzziness + 6.559971809387207 + ShadowVector + {1, 1} + + stroke + + Draws + NO + + + VFlip + YES + + + ID + 43 + Layer + 1 + VFlip + YES + + + Bounds + {{242.99996945271255, 45.774649604258862}, {93, 26}} + Class + ShapedGraphic + FitText + YES + Flow + Resize + ID + 208 + Layer + 1 + Shape + Rectangle + Style + + fill + + Draws + NO + + shadow + + Draws + NO + + stroke + + Draws + NO + + + Text + + Pad + 0 + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 OpenSans;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 Front View } + VerticalPad + 0 + + Wrap + NO + + + Bounds + {{809.73941206158747, 45.774649604259338}, {79, 26}} + Class + ShapedGraphic + FitText + YES + Flow + Resize + ID + 139 + Layer + 1 + Shape + Rectangle + Style + + fill + + Draws + NO + + shadow + + Draws + NO + + stroke + + Draws + NO + + + Text + + Pad + 0 + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 OpenSans;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs36 \cf0 Top View } + VerticalPad + 0 + + Wrap + NO + + + Bounds + {{978.23938675601653, 264.33125182518802}, {34, 38.5}} + Class + ShapedGraphic + ID + 130 + Layer + 1 + Shape + Rectangle + + + Bounds + {{687.23938675601585, 262.87654182518804}, {34, 38.5}} + Class + ShapedGraphic + ID + 1 + Layer + 1 + Shape + Rectangle + + + Bounds + {{942.59531675601613, 226.37654182518816}, {125.28812000000001, 30.5}} + Class + ShapedGraphic + HFlip + YES + ID + 129 + Layer + 1 + Shape + A5792564-7BB8-4CC5-9068-338E366F4CDF-643-000123FA94AC0471 + + + Bounds + {{630.95126275601592, 226.37654182518816}, {125.28812000000001, 30.5}} + Class + ShapedGraphic + ID + 128 + Layer + 1 + Shape + A5792564-7BB8-4CC5-9068-338E366F4CDF-643-000123FA94AC0471 + + + Bounds + {{879.23938675601585, 198.76546182518825}, {51, 17.611084000000002}} + Class + ShapedGraphic + ID + 118 + Layer + 1 + Shape + C23360B7-896D-4E43-821E-E438291A4B42-643-000123B31059F14D + + + Bounds + {{765.23938675601585, 198.76546182518825}, {51, 17.611084000000002}} + Class + ShapedGraphic + ID + 117 + Layer + 1 + Shape + C23360B7-896D-4E43-821E-E438291A4B42-643-000123B31059F14D + + + Bounds + {{641.73978375601587, 216.37654182518816}, {416.99918000000002, 97}} + Class + ShapedGraphic + ID + 113 + Layer + 1 + Shape + 47D48890-EFFB-40D4-9B16-B837B487F35B-643-000123949D248202 + + + Bounds + {{946.47890184399159, 205.37654838106059}, {87, 11}} + Class + ShapedGraphic + ID + 125 + Layer + 1 + Shape + Rectangle + + + Bounds + {{723.23945847751429, 205.37654182518818}, {24.522554, 11}} + Class + ShapedGraphic + ID + 124 + Layer + 1 + Shape + F1E2180B-1353-44D7-847D-FBDA9B734CEF-643-000123C8E8BEFFA9 + Style + + Text + + VerticalPad + 0 + + + + Bounds + {{667.73098603906249, 205.37654008460478}, {24.522554, 11}} + Class + ShapedGraphic + ID + 123 + Layer + 1 + Shape + F1E2180B-1353-44D7-847D-FBDA9B734CEF-643-000123C8E8BEFFA9 + Style + + Text + + VerticalPad + 0 + + + + Bounds + {{692.25351727169038, 203.33122213769531}, {28.981200999999999, 13.045318999999999}} + Class + ShapedGraphic + ID + 122 + Layer + 1 + Shape + F1E2180B-1353-44D7-847D-FBDA9B734CEF-643-000123C8E8BEFFA9 + Style + + Text + + VerticalPad + 0 + + + + Bounds + {{408.19366000000002, 212.95468}, {29.113309999999998, 28.843737000000001}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 12 + + ID + 69 + Layer + 2 + Shape + Circle + Style + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 OpenSans-Light;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 A} + VerticalPad + 0 + + + + Bounds + {{438.13702000000001, 185.56351000000001}, {29.113309999999998, 28.843737000000001}} + Class + ShapedGraphic + ID + 70 + Layer + 2 + Shape + Circle + Style + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 OpenSans;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 B} + VerticalPad + 0 + + + + Bounds + {{378.25031000000001, 185.56351000000001}, {29.113309999999998, 28.843737000000001}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 12 + + ID + 71 + Layer + 2 + Shape + Circle + Style + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 OpenSans;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 X} + VerticalPad + 0 + + + + Bounds + {{408.19366000000002, 157.75729000000001}, {29.113309999999998, 28.843737000000001}} + Class + ShapedGraphic + ID + 72 + Layer + 2 + Shape + Circle + Style + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 OpenSans;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 Y} + VerticalPad + 0 + + + + Bounds + {{320.50031000000001, 258.40285999999998}, {54, 53.5}} + Class + ShapedGraphic + ID + 73 + Layer + 2 + Shape + Circle + Style + + Text + + VerticalPad + 0 + + + + Bounds + {{108.50031, 155.40286}, {89, 89}} + Class + ShapedGraphic + ID + 74 + Layer + 2 + Shape + 3183FE08-E827-4DFB-B09C-26BEBDBFE644-643-00011FC72A7BAF86 + + + Bounds + {{201.00031000000001, 258.40285999999998}, {54, 53.5}} + Class + ShapedGraphic + ID + 75 + Layer + 2 + Shape + Circle + Style + + Text + + VerticalPad + 0 + + + + Bounds + {{82.500366, 105.36841}, {414.99921000000001, 273.53435999999999}} + Class + ShapedGraphic + ID + 76 + Layer + 2 + Shape + 1ECB1682-27C7-4AA3-B9D7-C873C7E35CC3-643-0001204E86C9927B + + + Bounds + {{359.61246, 96.000022999999999}, {112.00001, 48.902847000000001}} + Class + ShapedGraphic + HFlip + YES + ID + 77 + Layer + 2 + Shape + Bezier + ShapeData + + UnitPoints + + {0.49998891000000001, 0.34931803} + {0.50055384999999997, 0.13154410999999999} + {0.47943342, -0.11570501} + {0.42939042999999999, -0.25299692000000001} + {0.36383854999999998, -0.43283796000000002} + {0.016128421, -0.66722440999999999} + {-0.19835973000000001, -0.32486677000000003} + {-0.35491191999999999, -0.074985503999999994} + {-0.44506912999999998, 0.26552963000000002} + {-0.49746638999999998, 0.43138074999999998} + {-0.55840153000000003, 0.62424135000000003} + {0.49998891000000001, 0.34931803} + + + + + Bounds + {{104.50069000000001, 96.368431000000001}, {112.00001, 48.902847000000001}} + Class + ShapedGraphic + ID + 78 + Layer + 2 + Shape + Bezier + ShapeData + + UnitPoints + + {0.49998891000000001, 0.34931803} + {0.50055384999999997, 0.13154410999999999} + {0.47943342, -0.11570501} + {0.42939042999999999, -0.25299692000000001} + {0.36383854999999998, -0.43283796000000002} + {0.016128421, -0.66722440999999999} + {-0.19835973000000001, -0.32486677000000003} + {-0.35491191999999999, -0.074985503999999994} + {-0.44506912999999998, 0.26552963000000002} + {-0.49746638999999998, 0.43138074999999998} + {-0.55840153000000003, 0.62424135000000003} + {0.49998891000000001, 0.34931803} + + + + + GridInfo + + ShowsGrid + YES + + GuidesLocked + NO + GuidesVisible + YES + HPages + 2 + ImageCounter + 3 + KeepToScale + + Layers + + + Lock + NO + Name + Layer 3 + Print + YES + View + YES + + + Lock + NO + Name + Layer 2 + Print + YES + View + YES + + + Lock + NO + Name + Layer 1 + Print + YES + View + YES + + + LayoutInfo + + Animate + NO + circoMinDist + 18 + circoSeparation + 0.0 + layoutEngine + dot + neatoSeparation + 0.0 + twopiSeparation + 0.0 + + LinksVisible + NO + MagnetsVisible + NO + MasterSheets + + ModificationDate + 2014-01-22 02:45:32 +0000 + Modifier + Quddus Chong + NotesVisible + NO + Orientation + 2 + OriginVisible + NO + PageBreaks + YES + PrintInfo + + NSBottomMargin + + float + 41 + + NSHorizonalPagination + + int + 0 + + NSLeftMargin + + float + 18 + + NSPaperSize + + size + {612, 792} + + NSPrintReverseOrientation + + int + 0 + + NSRightMargin + + float + 18 + + NSTopMargin + + float + 18 + + + PrintOnePage + + ReadOnly + NO + RowAlign + 1 + RowSpacing + 36 + SheetTitle + Canvas 1 + SmartAlignmentGuidesActive + YES + SmartDistanceGuidesActive + YES + UniqueID + 1 + UseEntirePage + + VPages + 1 + WindowInfo + + CurrentSheet + 0 + ExpandedCanvases + + + name + Canvas 1 + + + Frame + {{4, 67}, {1436, 769}} + ListView + + OutlineWidth + 142 + RightSidebar + + ShowRuler + + Sidebar + + SidebarWidth + 120 + VisibleRegion + {{159.85915976085272, 0}, {991.54932574132454, 421.83099866410038}} + Zoom + 1.4199999570846558 + ZoomValues + + + Canvas 1 + 1.4199999570846558 + 0.70999997854232788 + + + + + -- cgit v1.2.3-59-g8ed1b