diff --git a/Externals/android-menudrawer/.gitignore b/Externals/android-menudrawer/.gitignore
new file mode 100644
index 0000000000..fdb137a854
--- /dev/null
+++ b/Externals/android-menudrawer/.gitignore
@@ -0,0 +1,30 @@
+/*
+
+!.gitignore
+!.travis.yml
+!CHANGELOG.md
+!README.md
+!LICENSE
+!checkstyle.xml
+!pom.xml
+
+!art/
+
+!library/
+library/*
+!library/src/
+!library/res/
+!library/AndroidManifest.xml
+!library/build.xml
+!library/pom.xml
+!library/project.properties
+
+!samples/
+samples/*
+!samples/src/
+!samples/res/
+!samples/libs/
+!samples/AndroidManifest.xml
+!samples/build.xml
+!samples/pom.xml
+!samples/project.properties
diff --git a/Externals/android-menudrawer/.travis.yml b/Externals/android-menudrawer/.travis.yml
new file mode 100644
index 0000000000..ee6ec5ca1f
--- /dev/null
+++ b/Externals/android-menudrawer/.travis.yml
@@ -0,0 +1,18 @@
+language: java
+
+notifications:
+ email: false
+
+before_install:
+ - wget http://dl.google.com/android/android-sdk_r21.0.1-linux.tgz
+ - tar -zxf android-sdk_r21.0.1-linux.tgz
+ - export ANDROID_HOME=~/builds/SimonVT/android-menudrawer/android-sdk-linux
+ - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
+ - TOOLS=$(android list sdk --no-ui | grep "Android SDK Platform-tools" | cut -d"-" -f1)
+ - android update sdk --filter "$TOOLS" --no-ui --force
+ - SDK=$(android list sdk --no-ui | grep ", API 16," | cut -d"-" -f1)
+ - android update sdk --filter "$SDK" --no-ui --force
+
+install:
+ - "mvn package --quiet -DskipTests"
+ - "mvn verify"
diff --git a/Externals/android-menudrawer/CHANGELOG.md b/Externals/android-menudrawer/CHANGELOG.md
new file mode 100644
index 0000000000..a570a8e420
--- /dev/null
+++ b/Externals/android-menudrawer/CHANGELOG.md
@@ -0,0 +1,44 @@
+Change Log
+==========
+
+Version 2.0.2 *(2013-03-31)*
+----------------------------
+ * Added listener that makes it possible to disabllow intercepting touch events over
+ certain views
+ * Added setter for the maximum animation duration
+ * Added getter for menu size
+ * Added methods that enable/disable indicator animation
+ * Fix: Removed log statements
+ * Fix: Drawing the active indicator might cause crash if the active view is not a
+ child of the MenuDrawer
+ * Fix: Crash in static drawer if no active indicator bitmap was set
+
+Version 2.0.1 *(2013-02-12)*
+----------------------------
+ * Indicator now animates between active views
+ * Fixed restoring state for right/bottom drawer
+
+Version 2.0.0 *(2013-01-23)*
+----------------------------
+
+ * Major API changes
+
+ * All classes are now in the net.simonvt.menudrawer package.
+ * MenuDrawerManager no longet exists. Menu is added with MenuDrawer#attach(...).
+ * Drawer position is now selected with Position enums instead of int constants.
+ * Width methods/attributes have been renamed to 'size'.
+
+ * Added top/bottom drawer.
+ * Added static (non-draggable, always visible) drawers.
+ * The touch bezel size is now configurable with MenuDrawer#setTouchBezelSize(int).
+ * MenuDrawer#saveState() now only required when dragging the entire window.
+ * Drawers can now be used in XML layouts.
+ * Fix: Scroller class caused conflicts with other libraries.
+ * Fix: No more overdraw when the drawer is closed.
+ * Fix: Content no longer falls behind when slowly dragging.
+
+
+Version 1.0.0 *(2012-10-30)*
+----------------------------
+
+Initial release.
diff --git a/Externals/android-menudrawer/LICENSE b/Externals/android-menudrawer/LICENSE
new file mode 100644
index 0000000000..7a4a3ea242
--- /dev/null
+++ b/Externals/android-menudrawer/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/Externals/android-menudrawer/README.md b/Externals/android-menudrawer/README.md
new file mode 100644
index 0000000000..1773274860
--- /dev/null
+++ b/Externals/android-menudrawer/README.md
@@ -0,0 +1,96 @@
+MenuDrawer
+==========
+
+A slide-out menu implementation, which allows users to navigate between views
+in your app. Most commonly the menu is revealed by either dragging the edge
+of the screen, or clicking the 'up' button in the action bar.
+
+
+Features
+--------
+
+ * The menu can be positioned along all four edges.
+ * Supports attaching an always visible, non-draggable menu, which is useful
+ on e.g. tablets.
+ * The menu can wrap both the content and the entire window.
+ * Allows the drawer to be opened by dragging the edge, the entire screen or
+ not at all.
+ * Can be used in XML layouts.
+ * Indicator that shows which screen is currently visible.
+
+
+Usage
+=====
+
+This library is very simple to use. It requires no extension of custom classes,
+it's simply added to an activity by calling one of the `MenuDrawer#attach(...)`
+methods.
+
+For more examples on how to use this library, check out the sample app.
+
+
+Left menu
+---------
+```java
+public class SampleActivity extends Activity {
+
+ private MenuDrawer mDrawer;
+
+ @Override
+ protected void onCreate(Bundle state) {
+ super.onCreate(state);
+ mDrawer = MenuDrawer.attach(this);
+ mDrawer.setContentView(R.layout.activity_sample);
+ mDrawer.setMenuView(R.layout.menu_sample);
+ }
+}
+```
+
+
+Right menu
+----------
+```java
+public class SampleActivity extends Activity {
+
+ private MenuDrawer mDrawer;
+
+ @Override
+ protected void onCreate(Bundle state) {
+ super.onCreate(state);
+ mDrawer = MenuDrawer.attach(this, Position.RIGHT);
+ mDrawer.setContentView(R.layout.activity_sample);
+ mDrawer.setMenuView(R.layout.menu_sample);
+ }
+}
+```
+
+
+Credits
+=======
+
+ * Cyril Mottier for his [articles][1] on the pattern
+
+
+License
+=======
+
+ Copyright 2012 Simon Vig Therkildsen
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+
+
+
+
+ [1]: http://android.cyrilmottier.com/?p=658
diff --git a/Externals/android-menudrawer/art/menu_arrow.svg b/Externals/android-menudrawer/art/menu_arrow.svg
new file mode 100644
index 0000000000..05f205a3bf
--- /dev/null
+++ b/Externals/android-menudrawer/art/menu_arrow.svg
@@ -0,0 +1,84 @@
+
+
+
+
diff --git a/Externals/android-menudrawer/checkstyle.xml b/Externals/android-menudrawer/checkstyle.xml
new file mode 100644
index 0000000000..beb2c4a7e6
--- /dev/null
+++ b/Externals/android-menudrawer/checkstyle.xml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/library/AndroidManifest.xml b/Externals/android-menudrawer/library/AndroidManifest.xml
new file mode 100644
index 0000000000..a77d28a931
--- /dev/null
+++ b/Externals/android-menudrawer/library/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/Externals/android-menudrawer/library/build.xml b/Externals/android-menudrawer/library/build.xml
new file mode 100644
index 0000000000..e5eb1dc0d0
--- /dev/null
+++ b/Externals/android-menudrawer/library/build.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/library/pom.xml b/Externals/android-menudrawer/library/pom.xml
new file mode 100644
index 0000000000..b5bd881040
--- /dev/null
+++ b/Externals/android-menudrawer/library/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+ 4.0.0
+
+
+ net.simonvt
+ android-menudrawer-parent
+ 2.0.3-SNAPSHOT
+ ../pom.xml
+
+
+ android-menudrawer
+ Android MenuDrawer
+ apklib
+
+
+
+ com.google.android
+ android
+ provided
+
+
+
+
+ src
+
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+ true
+
+
+
+
diff --git a/Externals/android-menudrawer/library/project.properties b/Externals/android-menudrawer/library/project.properties
new file mode 100644
index 0000000000..c81c2d97f5
--- /dev/null
+++ b/Externals/android-menudrawer/library/project.properties
@@ -0,0 +1,16 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+android.library=true
+# Project target.
+target=android-17
+
diff --git a/Externals/android-menudrawer/library/res/values/attrs.xml b/Externals/android-menudrawer/library/res/values/attrs.xml
new file mode 100644
index 0000000000..c8ec779700
--- /dev/null
+++ b/Externals/android-menudrawer/library/res/values/attrs.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/library/res/values/colors.xml b/Externals/android-menudrawer/library/res/values/colors.xml
new file mode 100644
index 0000000000..2866c6abe6
--- /dev/null
+++ b/Externals/android-menudrawer/library/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+
+ #FF555555
+
+
diff --git a/Externals/android-menudrawer/library/res/values/ids.xml b/Externals/android-menudrawer/library/res/values/ids.xml
new file mode 100644
index 0000000000..c6001910b0
--- /dev/null
+++ b/Externals/android-menudrawer/library/res/values/ids.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/library/res/values/styles.xml b/Externals/android-menudrawer/library/res/values/styles.xml
new file mode 100644
index 0000000000..415be6d229
--- /dev/null
+++ b/Externals/android-menudrawer/library/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BottomDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BottomDrawer.java
new file mode 100644
index 0000000000..17f41e2c4c
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BottomDrawer.java
@@ -0,0 +1,226 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+public class BottomDrawer extends VerticalDrawer {
+
+ private int mIndicatorLeft;
+
+ BottomDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public BottomDrawer(Context context) {
+ super(context);
+ }
+
+ public BottomDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BottomDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void openMenu(boolean animate) {
+ animateOffsetTo(-mMenuSize, 0, animate);
+ }
+
+ @Override
+ public void closeMenu(boolean animate) {
+ animateOffsetTo(0, 0, animate);
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
+ new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int width = r - l;
+ final int height = b - t;
+ final int offsetPixels = (int) mOffsetPixels;
+ final int menuSize = mMenuSize;
+
+ mMenuContainer.layout(0, height - menuSize, width, height);
+ offsetMenu(offsetPixels);
+
+ if (USE_TRANSLATIONS) {
+ mContentContainer.layout(0, 0, width, height);
+ } else {
+ mContentContainer.layout(0, offsetPixels, width, height + offsetPixels);
+ }
+ }
+
+ /**
+ * Offsets the menu relative to its original position based on the position of the content.
+ *
+ * @param offsetPixels The number of pixels the content if offset.
+ */
+ private void offsetMenu(int offsetPixels) {
+ if (mOffsetMenu && mMenuSize != 0) {
+ final int height = getHeight();
+ final int menuSize = mMenuSize;
+ final float openRatio = (menuSize + (float) offsetPixels) / menuSize;
+
+ if (USE_TRANSLATIONS) {
+ if (offsetPixels != 0) {
+ final int offset = (int) (0.25f * (openRatio * menuSize));
+ mMenuContainer.setTranslationY(offset);
+ } else {
+ mMenuContainer.setTranslationY(height + menuSize);
+ }
+
+ } else {
+ final int oldMenuTop = mMenuContainer.getTop();
+ final int offsetBy = (int) (0.25f * (openRatio * menuSize));
+ final int offset = height - mMenuSize + offsetBy - oldMenuTop;
+ mMenuContainer.offsetTopAndBottom(offset);
+ mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ protected void drawDropShadow(Canvas canvas, int offsetPixels) {
+ final int width = getWidth();
+ final int height = getHeight();
+
+ mDropShadowDrawable.setBounds(0, height + offsetPixels, width, height + offsetPixels + mDropShadowSize);
+ mDropShadowDrawable.draw(canvas);
+ }
+
+ @Override
+ protected void drawMenuOverlay(Canvas canvas, int offsetPixels) {
+ final int width = getWidth();
+ final int height = getHeight();
+ final float openRatio = ((float) Math.abs(offsetPixels)) / mMenuSize;
+
+ mMenuOverlay.setBounds(0, height + offsetPixels, width, height);
+ mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio)));
+ mMenuOverlay.draw(canvas);
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas, int offsetPixels) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ final int height = getHeight();
+ final int menuHeight = mMenuSize;
+ final int indicatorHeight = mActiveIndicator.getHeight();
+
+ final float openRatio = ((float) Math.abs(offsetPixels)) / menuHeight;
+
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+ final int indicatorWidth = mActiveIndicator.getWidth();
+
+ final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio));
+ final int interpolatedHeight = (int) (indicatorHeight * interpolatedRatio);
+
+ final int indicatorBottom = height + offsetPixels + interpolatedHeight;
+ final int indicatorTop = indicatorBottom - indicatorHeight;
+ if (mIndicatorAnimating) {
+ final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ final int startLeft = mIndicatorStartPos;
+ final int diff = finalLeft - startLeft;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorLeft = startLeft + startOffset;
+ } else {
+ mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ }
+
+ canvas.save();
+ canvas.clipRect(mIndicatorLeft, height + offsetPixels, mIndicatorLeft + indicatorWidth,
+ indicatorBottom);
+ canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorLeft;
+ }
+
+ @Override
+ protected void initPeekScroller() {
+ final int dx = -mMenuSize / 3;
+ mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
+ }
+
+ @Override
+ protected void onOffsetPixelsChanged(int offsetPixels) {
+ if (USE_TRANSLATIONS) {
+ mContentContainer.setTranslationY(offsetPixels);
+ offsetMenu(offsetPixels);
+ invalidate();
+ } else {
+ mContentContainer.offsetTopAndBottom(offsetPixels - mContentContainer.getTop());
+ offsetMenu(offsetPixels);
+ invalidate();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // Touch handling
+ //////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected boolean isContentTouch(MotionEvent ev) {
+ return ev.getY() < getHeight() + mOffsetPixels;
+ }
+
+ @Override
+ protected boolean onDownAllowDrag(MotionEvent ev) {
+ final int height = getHeight();
+ return (!mMenuVisible && mInitialMotionY >= height - mTouchSize)
+ || (mMenuVisible && mInitialMotionY <= height + mOffsetPixels);
+ }
+
+ @Override
+ protected boolean onMoveAllowDrag(MotionEvent ev, float diff) {
+ final int height = getHeight();
+ return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (diff < 0))
+ || (mMenuVisible && mInitialMotionY <= height + mOffsetPixels);
+ }
+
+ @Override
+ protected void onMoveEvent(float dx) {
+ setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize));
+ }
+
+ @Override
+ protected void onUpEvent(MotionEvent ev) {
+ final int offsetPixels = (int) mOffsetPixels;
+
+ if (mIsDragging) {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ final int initialVelocity = (int) mVelocityTracker.getXVelocity();
+ mLastMotionY = ev.getY();
+ animateOffsetTo(mVelocityTracker.getYVelocity() < 0 ? -mMenuSize : 0, initialVelocity,
+ true);
+
+ // Close the menu when content is clicked while the menu is visible.
+ } else if (mMenuVisible && ev.getY() < getHeight() + offsetPixels) {
+ closeMenu();
+ }
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BottomStaticDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BottomStaticDrawer.java
new file mode 100644
index 0000000000..df4b5f4997
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BottomStaticDrawer.java
@@ -0,0 +1,85 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+
+public class BottomStaticDrawer extends StaticDrawer {
+
+ private int mIndicatorLeft;
+
+ BottomStaticDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public BottomStaticDrawer(Context context) {
+ super(context);
+ }
+
+ public BottomStaticDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BottomStaticDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super.initDrawer(context, attrs, defStyle);
+ mPosition = Position.BOTTOM;
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ final int height = getHeight();
+ final int menuHeight = mMenuSize;
+ final int indicatorHeight = mActiveIndicator.getHeight();
+
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+ final int indicatorWidth = mActiveIndicator.getWidth();
+
+ final int indicatorTop = height - menuHeight;
+ final int indicatorBottom = indicatorTop + indicatorHeight;
+ if (mIndicatorAnimating) {
+ final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ final int startLeft = mIndicatorStartPos;
+ final int diff = finalLeft - startLeft;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorLeft = startLeft + startOffset;
+ } else {
+ mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ }
+
+ canvas.save();
+ canvas.clipRect(mIndicatorLeft, indicatorTop, mIndicatorLeft + indicatorWidth,
+ indicatorBottom);
+ canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorLeft;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BuildLayerFrameLayout.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BuildLayerFrameLayout.java
new file mode 100644
index 0000000000..45f5aa13c5
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/BuildLayerFrameLayout.java
@@ -0,0 +1,99 @@
+package net.simonvt.menudrawer;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * FrameLayout which caches the hardware layer if available.
+ *
+ * If it's not posted twice the layer either wont be built on start, or it'll be built twice.
+ */
+public class BuildLayerFrameLayout extends FrameLayout {
+
+ private boolean mChanged;
+
+ private boolean mHardwareLayersEnabled = true;
+
+ private boolean mAttached;
+
+ private boolean mFirst = true;
+
+ public BuildLayerFrameLayout(Context context) {
+ super(context);
+ if (MenuDrawer.USE_TRANSLATIONS) {
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+ }
+
+ public BuildLayerFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (MenuDrawer.USE_TRANSLATIONS) {
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+ }
+
+ public BuildLayerFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (MenuDrawer.USE_TRANSLATIONS) {
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+ }
+
+ void setHardwareLayersEnabled(boolean enabled) {
+ mHardwareLayersEnabled = enabled;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAttached = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mAttached = false;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ if (MenuDrawer.USE_TRANSLATIONS && mHardwareLayersEnabled) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mChanged = true;
+ invalidate();
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (mChanged && MenuDrawer.USE_TRANSLATIONS) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mAttached) {
+ final int layerType = getLayerType();
+ // If it's already a hardware layer, it'll be built anyway.
+ if (layerType != LAYER_TYPE_HARDWARE || mFirst) {
+ mFirst = false;
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ buildLayer();
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+ }
+ }
+ });
+
+ mChanged = false;
+ }
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/ColorDrawable.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/ColorDrawable.java
new file mode 100644
index 0000000000..10a35e7fae
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/ColorDrawable.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.simonvt.menudrawer;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A specialized Drawable that fills the Canvas with a specified color.
+ * Note that a ColorDrawable ignores the ColorFilter.
+ *
+ *
It can be defined in an XML file with the <color> element.
+ *
+ * @attr ref android.R.styleable#ColorDrawable_color
+ */
+public class ColorDrawable extends Drawable {
+
+ private ColorState mState;
+ private final Paint mPaint = new Paint();
+
+ /** Creates a new black ColorDrawable. */
+ public ColorDrawable() {
+ this(null);
+ }
+
+ /**
+ * Creates a new ColorDrawable with the specified color.
+ *
+ * @param color The color to draw.
+ */
+ public ColorDrawable(int color) {
+ this(null);
+ setColor(color);
+ }
+
+ private ColorDrawable(ColorState state) {
+ mState = new ColorState(state);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return super.getChangingConfigurations() | mState.mChangingConfigurations;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if ((mState.mUseColor >>> 24) != 0) {
+ mPaint.setColor(mState.mUseColor);
+ canvas.drawRect(getBounds(), mPaint);
+ }
+ }
+
+ /**
+ * Gets the drawable's color value.
+ *
+ * @return int The color to draw.
+ */
+ public int getColor() {
+ return mState.mUseColor;
+ }
+
+ /**
+ * Sets the drawable's color value. This action will clobber the results of prior calls to
+ * {@link #setAlpha(int)} on this object, which side-affected the underlying color.
+ *
+ * @param color The color to draw.
+ */
+ public void setColor(int color) {
+ if (mState.mBaseColor != color || mState.mUseColor != color) {
+ invalidateSelf();
+ mState.mBaseColor = mState.mUseColor = color;
+ }
+ }
+
+ /**
+ * Returns the alpha value of this drawable's color.
+ *
+ * @return A value between 0 and 255.
+ */
+ public int getAlpha() {
+ return mState.mUseColor >>> 24;
+ }
+
+ /**
+ * Sets the color's alpha value.
+ *
+ * @param alpha The alpha value to set, between 0 and 255.
+ */
+ public void setAlpha(int alpha) {
+ alpha += alpha >> 7; // make it 0..256
+ int baseAlpha = mState.mBaseColor >>> 24;
+ int useAlpha = baseAlpha * alpha >> 8;
+ int oldUseColor = mState.mUseColor;
+ mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
+ if (oldUseColor != mState.mUseColor) {
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Setting a color filter on a ColorDrawable has no effect.
+ *
+ * @param colorFilter Ignore.
+ */
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ public int getOpacity() {
+ switch (mState.mUseColor >>> 24) {
+ case 255:
+ return PixelFormat.OPAQUE;
+ case 0:
+ return PixelFormat.TRANSPARENT;
+ }
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ mState.mChangingConfigurations = getChangingConfigurations();
+ return mState;
+ }
+
+ static final class ColorState extends ConstantState {
+
+ int mBaseColor; // base color, independent of setAlpha()
+ int mUseColor; // basecolor modulated by setAlpha()
+ int mChangingConfigurations;
+
+ ColorState(ColorState state) {
+ if (state != null) {
+ mBaseColor = state.mBaseColor;
+ mUseColor = state.mUseColor;
+ }
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new ColorDrawable(this);
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ return new ColorDrawable(this);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/DraggableDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/DraggableDrawer.java
new file mode 100644
index 0000000000..77fc98e5d3
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/DraggableDrawer.java
@@ -0,0 +1,672 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+
+public abstract class DraggableDrawer extends MenuDrawer {
+
+ /**
+ * Key used when saving menu visibility state.
+ */
+ private static final String STATE_MENU_VISIBLE = "net.simonvt.menudrawer.MenuDrawer.menuVisible";
+
+ /**
+ * Interpolator used for stretching/retracting the active indicator.
+ */
+ protected static final Interpolator INDICATOR_INTERPOLATOR = new AccelerateInterpolator();
+
+ /**
+ * Interpolator used for peeking at the drawer.
+ */
+ private static final Interpolator PEEK_INTERPOLATOR = new PeekInterpolator();
+
+ /**
+ * The maximum alpha of the dark menu overlay used for dimming the menu.
+ */
+ protected static final int MAX_MENU_OVERLAY_ALPHA = 185;
+
+ /**
+ * Default delay from {@link #peekDrawer()} is called until first animation is run.
+ */
+ private static final long DEFAULT_PEEK_START_DELAY = 5000;
+
+ /**
+ * Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
+ */
+ private static final long DEFAULT_PEEK_DELAY = 10000;
+
+ /**
+ * The duration of the peek animation.
+ */
+ protected static final int PEEK_DURATION = 5000;
+
+ /**
+ * Distance in dp from closed position from where the drawer is considered closed with regards to touch events.
+ */
+ private static final int CLOSE_ENOUGH = 3;
+
+ /**
+ * Slop before starting a drag.
+ */
+ protected int mTouchSlop;
+
+ /**
+ * Runnable used when the peek animation is running.
+ */
+ protected final Runnable mPeekRunnable = new Runnable() {
+ @Override
+ public void run() {
+ peekDrawerInvalidate();
+ }
+ };
+
+ /**
+ * Runnable used when animating the drawer open/closed.
+ */
+ private final Runnable mDragRunnable = new Runnable() {
+ @Override
+ public void run() {
+ postAnimationInvalidate();
+ }
+ };
+
+ /**
+ * Current left position of the content.
+ */
+ protected float mOffsetPixels;
+
+ /**
+ * Indicates whether the drawer is currently being dragged.
+ */
+ protected boolean mIsDragging;
+
+ /**
+ * The initial X position of a drag.
+ */
+ protected float mInitialMotionX;
+
+ /**
+ * The initial Y position of a drag.
+ */
+ protected float mInitialMotionY;
+
+ /**
+ * The last X position of a drag.
+ */
+ protected float mLastMotionX = -1;
+
+ /**
+ * The last Y position of a drag.
+ */
+ protected float mLastMotionY = -1;
+
+ /**
+ * Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
+ */
+ protected long mPeekDelay;
+
+ /**
+ * Scroller used for the peek drawer animation.
+ */
+ protected Scroller mPeekScroller;
+
+ /**
+ * Velocity tracker used when animating the drawer open/closed after a drag.
+ */
+ protected VelocityTracker mVelocityTracker;
+
+ /**
+ * Maximum velocity allowed when animating the drawer open/closed.
+ */
+ protected int mMaxVelocity;
+
+ /**
+ * Indicates whether the menu should be offset when dragging the drawer.
+ */
+ protected boolean mOffsetMenu = true;
+
+ /**
+ * Distance in px from closed position from where the drawer is considered closed with regards to touch events.
+ */
+ protected int mCloseEnough;
+
+ /**
+ * Runnable used for first call to {@link #startPeek()} after {@link #peekDrawer()} has been called.
+ */
+ private Runnable mPeekStartRunnable;
+
+ /**
+ * Scroller used when animating the drawer open/closed.
+ */
+ private Scroller mScroller;
+
+ /**
+ * Indicates whether the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}.
+ */
+ private boolean mLayerTypeHardware;
+
+ DraggableDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public DraggableDrawer(Context context) {
+ super(context);
+ }
+
+ public DraggableDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DraggableDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super.initDrawer(context, attrs, defStyle);
+
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
+
+ mScroller = new Scroller(context, MenuDrawer.SMOOTH_INTERPOLATOR);
+ mPeekScroller = new Scroller(context, DraggableDrawer.PEEK_INTERPOLATOR);
+
+ mCloseEnough = dpToPx(DraggableDrawer.CLOSE_ENOUGH);
+ }
+
+ public void toggleMenu(boolean animate) {
+ if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
+ closeMenu(animate);
+ } else if (mDrawerState == STATE_CLOSED || mDrawerState == STATE_CLOSING) {
+ openMenu(animate);
+ }
+ }
+
+ public boolean isMenuVisible() {
+ return mMenuVisible;
+ }
+
+ public void setMenuSize(final int size) {
+ mMenuSize = size;
+ mMenuSizeSet = true;
+ if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
+ setOffsetPixels(mMenuSize);
+ }
+ requestLayout();
+ invalidate();
+ }
+
+ public void setOffsetMenuEnabled(boolean offsetMenu) {
+ if (offsetMenu != mOffsetMenu) {
+ mOffsetMenu = offsetMenu;
+ requestLayout();
+ invalidate();
+ }
+ }
+
+ public boolean getOffsetMenuEnabled() {
+ return mOffsetMenu;
+ }
+
+ public void peekDrawer() {
+ peekDrawer(DEFAULT_PEEK_START_DELAY, DEFAULT_PEEK_DELAY);
+ }
+
+ public void peekDrawer(long delay) {
+ peekDrawer(DEFAULT_PEEK_START_DELAY, delay);
+ }
+
+ public void peekDrawer(final long startDelay, final long delay) {
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("startDelay must be zero or larger.");
+ }
+ if (delay < 0) {
+ throw new IllegalArgumentException("delay must be zero or larger");
+ }
+
+ removeCallbacks(mPeekRunnable);
+ removeCallbacks(mPeekStartRunnable);
+
+ mPeekDelay = delay;
+ mPeekStartRunnable = new Runnable() {
+ @Override
+ public void run() {
+ startPeek();
+ }
+ };
+ postDelayed(mPeekStartRunnable, startDelay);
+ }
+
+ public void setHardwareLayerEnabled(boolean enabled) {
+ if (enabled != mHardwareLayersEnabled) {
+ mHardwareLayersEnabled = enabled;
+ mMenuContainer.setHardwareLayersEnabled(enabled);
+ mContentContainer.setHardwareLayersEnabled(enabled);
+ stopLayerTranslation();
+ }
+ }
+
+ public int getTouchMode() {
+ return mTouchMode;
+ }
+
+ public void setTouchMode(int mode) {
+ if (mTouchMode != mode) {
+ mTouchMode = mode;
+ updateTouchAreaSize();
+ }
+ }
+
+ public void setTouchBezelSize(int size) {
+ mTouchBezelSize = size;
+ }
+
+ public int getTouchBezelSize() {
+ return mTouchBezelSize;
+ }
+
+ /**
+ * Sets the number of pixels the content should be offset.
+ *
+ * @param offsetPixels The number of pixels to offset the content by.
+ */
+ protected void setOffsetPixels(float offsetPixels) {
+ final int oldOffset = (int) mOffsetPixels;
+ final int newOffset = (int) offsetPixels;
+
+ mOffsetPixels = offsetPixels;
+
+ if (newOffset != oldOffset) {
+ onOffsetPixelsChanged(newOffset);
+ mMenuVisible = newOffset != 0;
+ }
+ }
+
+ /**
+ * Called when the number of pixels the content should be offset by has changed.
+ *
+ * @param offsetPixels The number of pixels to offset the content by.
+ */
+ protected abstract void onOffsetPixelsChanged(int offsetPixels);
+
+ /**
+ * If possible, set the layer type to {@link android.view.View#LAYER_TYPE_HARDWARE}.
+ */
+ protected void startLayerTranslation() {
+ if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
+ mLayerTypeHardware = true;
+ mContentContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ }
+
+ /**
+ * If the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}, this will set it to
+ * {@link View#LAYER_TYPE_NONE}.
+ */
+ private void stopLayerTranslation() {
+ if (mLayerTypeHardware) {
+ mLayerTypeHardware = false;
+ mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
+ mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ /**
+ * Compute the touch area based on the touch mode.
+ */
+ protected void updateTouchAreaSize() {
+ if (mTouchMode == TOUCH_MODE_BEZEL) {
+ mTouchSize = mTouchBezelSize;
+ } else if (mTouchMode == TOUCH_MODE_FULLSCREEN) {
+ mTouchSize = getMeasuredWidth();
+ } else {
+ mTouchSize = 0;
+ }
+ }
+
+ /**
+ * Called when a drag has been ended.
+ */
+ protected void endDrag() {
+ mIsDragging = false;
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * Stops ongoing animation of the drawer.
+ */
+ protected void stopAnimation() {
+ removeCallbacks(mDragRunnable);
+ mScroller.abortAnimation();
+ stopLayerTranslation();
+ }
+
+ /**
+ * Called when a drawer animation has successfully completed.
+ */
+ private void completeAnimation() {
+ mScroller.abortAnimation();
+ final int finalX = mScroller.getFinalX();
+ setOffsetPixels(finalX);
+ setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN);
+ stopLayerTranslation();
+ }
+
+ /**
+ * Moves the drawer to the position passed.
+ *
+ * @param position The position the content is moved to.
+ * @param velocity Optional velocity if called by releasing a drag event.
+ * @param animate Whether the move is animated.
+ */
+ protected void animateOffsetTo(int position, int velocity, boolean animate) {
+ endDrag();
+ endPeek();
+
+ final int startX = (int) mOffsetPixels;
+ final int dx = position - startX;
+ if (dx == 0 || !animate) {
+ setOffsetPixels(position);
+ setDrawerState(position == 0 ? STATE_CLOSED : STATE_OPEN);
+ stopLayerTranslation();
+ return;
+ }
+
+ int duration;
+
+ velocity = Math.abs(velocity);
+ if (velocity > 0) {
+ duration = 4 * Math.round(1000.f * Math.abs((float) dx / velocity));
+ } else {
+ duration = (int) (600.f * Math.abs((float) dx / mMenuSize));
+ }
+
+ duration = Math.min(duration, mMaxAnimationDuration);
+
+ if (dx > 0) {
+ setDrawerState(STATE_OPENING);
+ mScroller.startScroll(startX, 0, dx, 0, duration);
+ } else {
+ setDrawerState(STATE_CLOSING);
+ mScroller.startScroll(startX, 0, dx, 0, duration);
+ }
+
+ startLayerTranslation();
+
+ postAnimationInvalidate();
+ }
+
+ /**
+ * Callback when each frame in the drawer animation should be drawn.
+ */
+ private void postAnimationInvalidate() {
+ if (mScroller.computeScrollOffset()) {
+ final int oldX = (int) mOffsetPixels;
+ final int x = mScroller.getCurrX();
+
+ if (x != oldX) setOffsetPixels(x);
+ if (x != mScroller.getFinalX()) {
+ postOnAnimation(mDragRunnable);
+ return;
+ }
+ }
+
+ completeAnimation();
+ }
+
+ /**
+ * Starts peek drawer animation.
+ */
+ protected void startPeek() {
+ initPeekScroller();
+
+ startLayerTranslation();
+ peekDrawerInvalidate();
+ }
+
+ protected abstract void initPeekScroller();
+
+ /**
+ * Callback when each frame in the peek drawer animation should be drawn.
+ */
+ private void peekDrawerInvalidate() {
+ if (mPeekScroller.computeScrollOffset()) {
+ final int oldX = (int) mOffsetPixels;
+ final int x = mPeekScroller.getCurrX();
+ if (x != oldX) setOffsetPixels(x);
+
+ if (!mPeekScroller.isFinished()) {
+ postOnAnimation(mPeekRunnable);
+ return;
+
+ } else if (mPeekDelay > 0) {
+ mPeekStartRunnable = new Runnable() {
+ @Override
+ public void run() {
+ startPeek();
+ }
+ };
+ postDelayed(mPeekStartRunnable, mPeekDelay);
+ }
+ }
+
+ completePeek();
+ }
+
+ /**
+ * Called when the peek drawer animation has successfully completed.
+ */
+ private void completePeek() {
+ mPeekScroller.abortAnimation();
+
+ setOffsetPixels(0);
+
+ setDrawerState(STATE_CLOSED);
+ stopLayerTranslation();
+ }
+
+ /**
+ * Stops ongoing peek drawer animation.
+ */
+ protected void endPeek() {
+ removeCallbacks(mPeekStartRunnable);
+ removeCallbacks(mPeekRunnable);
+ stopLayerTranslation();
+ }
+
+ protected boolean isCloseEnough() {
+ return Math.abs(mOffsetPixels) <= mCloseEnough;
+ }
+
+ /**
+ * Returns true if the touch event occurs over the content.
+ *
+ * @param ev The motion event.
+ * @return True if the touch event occurred over the content, false otherwise.
+ */
+ protected abstract boolean isContentTouch(MotionEvent ev);
+
+ /**
+ * Returns true if dragging the content should be allowed.
+ *
+ * @param ev The motion event.
+ * @return True if dragging the content should be allowed, false otherwise.
+ */
+ protected abstract boolean onDownAllowDrag(MotionEvent ev);
+
+ /**
+ * Tests scrollability within child views of v given a delta of dx.
+ *
+ * @param v View to test for horizontal scrollability
+ * @param checkV Whether the view should be checked for draggability
+ * @param dx Delta scrolled in pixels
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if child views of v can be scrolled by delta of dx.
+ */
+ protected boolean canChildScrollHorizontally(View v, boolean checkV, int dx, int x, int y) {
+ if (v instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) v;
+
+ final int count = group.getChildCount();
+ // Count backwards - let topmost views consume scroll distance first.
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = group.getChildAt(i);
+
+ final int childLeft = child.getLeft() + supportGetTranslationX(child);
+ final int childRight = child.getRight() + supportGetTranslationX(child);
+ final int childTop = child.getTop() + supportGetTranslationY(child);
+ final int childBottom = child.getBottom() + supportGetTranslationY(child);
+
+ if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
+ && canChildScrollHorizontally(child, true, dx, x - childLeft, y - childTop)) {
+ return true;
+ }
+ }
+ }
+
+ return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
+ }
+
+ /**
+ * Tests scrollability within child views of v given a delta of dx.
+ *
+ * @param v View to test for horizontal scrollability
+ * @param checkV Whether the view should be checked for draggability
+ * @param dx Delta scrolled in pixels
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if child views of v can be scrolled by delta of dx.
+ */
+ protected boolean canChildScrollVertically(View v, boolean checkV, int dx, int x, int y) {
+ if (v instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) v;
+
+ final int count = group.getChildCount();
+ // Count backwards - let topmost views consume scroll distance first.
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = group.getChildAt(i);
+
+ final int childLeft = child.getLeft() + supportGetTranslationX(child);
+ final int childRight = child.getRight() + supportGetTranslationX(child);
+ final int childTop = child.getTop() + supportGetTranslationY(child);
+ final int childBottom = child.getBottom() + supportGetTranslationY(child);
+
+ if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
+ && canChildScrollVertically(child, true, dx, x - childLeft, y - childTop)) {
+ return true;
+ }
+ }
+ }
+
+ return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
+ }
+
+ private int supportGetTranslationY(View v) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ return (int) v.getTranslationY();
+ }
+
+ return 0;
+ }
+
+ private int supportGetTranslationX(View v) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ return (int) v.getTranslationX();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns true if dragging the content should be allowed.
+ *
+ * @param ev The motion event.
+ * @return True if dragging the content should be allowed, false otherwise.
+ */
+ protected abstract boolean onMoveAllowDrag(MotionEvent ev, float dx);
+
+ /**
+ * Called when a move event has happened while dragging the content is in progress.
+ *
+ * @param dx The X difference between the last motion event and the current motion event.
+ */
+ protected abstract void onMoveEvent(float dx);
+
+ /**
+ * Called when {@link android.view.MotionEvent#ACTION_UP} of {@link android.view.MotionEvent#ACTION_CANCEL} is
+ * delivered to {@link net.simonvt.menudrawer.MenuDrawer#onTouchEvent(android.view.MotionEvent)}.
+ *
+ * @param ev The motion event.
+ */
+ protected abstract void onUpEvent(MotionEvent ev);
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ final int offsetPixels = (int) mOffsetPixels;
+
+ if (offsetPixels != 0) drawMenuOverlay(canvas, offsetPixels);
+ if (mDropShadowEnabled) drawDropShadow(canvas, offsetPixels);
+ if (mActiveIndicator != null) drawIndicator(canvas, offsetPixels);
+ }
+
+ /**
+ * Called when the content drop shadow should be drawn.
+ *
+ * @param canvas The canvas on which to draw.
+ * @param offsetPixels Value in pixels indicating the offset.
+ */
+ protected abstract void drawDropShadow(Canvas canvas, int offsetPixels);
+
+ /**
+ * Called when the menu overlay should be drawn.
+ *
+ * @param canvas The canvas on which to draw.
+ * @param offsetPixels Value in pixels indicating the offset.
+ */
+ protected abstract void drawMenuOverlay(Canvas canvas, int offsetPixels);
+
+ /**
+ * Called when the active indicator should be drawn.
+ *
+ * @param canvas The canvas on which to draw.
+ * @param offsetPixels Value in pixels indicating the offset.
+ */
+ protected abstract void drawIndicator(Canvas canvas, int offsetPixels);
+
+ void saveState(Bundle state) {
+ final boolean menuVisible = mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING;
+ state.putBoolean(STATE_MENU_VISIBLE, menuVisible);
+ }
+
+ public void restoreState(Parcelable in) {
+ super.restoreState(in);
+ Bundle state = (Bundle) in;
+ final boolean menuOpen = state.getBoolean(STATE_MENU_VISIBLE);
+ if (menuOpen) {
+ openMenu(false);
+ } else {
+ setOffsetPixels(0);
+ }
+ mDrawerState = menuOpen ? STATE_OPEN : STATE_CLOSED;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/FloatScroller.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/FloatScroller.java
new file mode 100644
index 0000000000..df5b445cd7
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/FloatScroller.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.simonvt.menudrawer;
+
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class encapsulates scrolling. The duration of the scroll
+ * can be passed in the constructor and specifies the maximum time that
+ * the scrolling animation should take. Past this time, the scrolling is
+ * automatically moved to its final stage and computeScrollOffset()
+ * will always return false to indicate that scrolling is over.
+ */
+public class FloatScroller {
+
+ private float mStart;
+ private float mFinal;
+
+ private float mCurr;
+ private long mStartTime;
+ private int mDuration;
+ private float mDurationReciprocal;
+ private float mDeltaX;
+ private boolean mFinished;
+ private Interpolator mInterpolator;
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. Specify whether or
+ * not to support progressive "flywheel" behavior in flinging.
+ */
+ public FloatScroller(Interpolator interpolator) {
+ mFinished = true;
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mFinished = finished;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ */
+ public final int getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Returns the current offset in the scroll.
+ *
+ * @return The new offset as an absolute distance from the origin.
+ */
+ public final float getCurr() {
+ return mCurr;
+ }
+
+ /**
+ * Returns the start offset in the scroll.
+ *
+ * @return The start offset as an absolute distance from the origin.
+ */
+ public final float getStart() {
+ return mStart;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final offset as an absolute distance from the origin.
+ */
+ public final float getFinal() {
+ return mFinal;
+ }
+
+ public boolean computeScrollOffset() {
+ if (mFinished) {
+ return false;
+ }
+
+ int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+
+ if (timePassed < mDuration) {
+ float x = timePassed * mDurationReciprocal;
+ x = mInterpolator.getInterpolation(x);
+ mCurr = mStart + x * mDeltaX;
+
+ } else {
+ mCurr = mFinal;
+ mFinished = true;
+ }
+ return true;
+ }
+
+ public void startScroll(float start, float delta, int duration) {
+ mFinished = false;
+ mDuration = duration;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStart = start;
+ mFinal = start + delta;
+ mDeltaX = delta;
+ mDurationReciprocal = 1.0f / (float) mDuration;
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating cause the scroller to move to the final x and y
+ * position
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mCurr = mFinal;
+ mFinished = true;
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinal(float)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinal(float)
+ */
+ public void extendDuration(int extend) {
+ int passed = timePassed();
+ mDuration = passed + extend;
+ mDurationReciprocal = 1.0f / mDuration;
+ mFinished = false;
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ */
+ public int timePassed() {
+ return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+ }
+
+ public void setFinal(float newVal) {
+ mFinal = newVal;
+ mDeltaX = mFinal - mStart;
+ mFinished = false;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/HorizontalDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/HorizontalDrawer.java
new file mode 100644
index 0000000000..6f1c8300f7
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/HorizontalDrawer.java
@@ -0,0 +1,207 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+public abstract class HorizontalDrawer extends DraggableDrawer {
+
+ private static final String TAG = "HorizontalDrawer";
+
+ HorizontalDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public HorizontalDrawer(Context context) {
+ super(context);
+ }
+
+ public HorizontalDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public HorizontalDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Must measure with an exact size");
+ }
+
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (!mMenuSizeSet) mMenuSize = (int) (width * 0.8f);
+ if (mOffsetPixels == -1) openMenu(false);
+
+ final int menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
+ final int menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
+ mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
+
+ final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
+ final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
+ mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
+
+ setMeasuredDimension(width, height);
+
+ updateTouchAreaSize();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
+ setOffsetPixels(0);
+ stopAnimation();
+ endPeek();
+ setDrawerState(STATE_CLOSED);
+ }
+
+ // Always intercept events over the content while menu is visible.
+ if (mMenuVisible && isContentTouch(ev)) return true;
+
+ if (mTouchMode == TOUCH_MODE_NONE) {
+ return false;
+ }
+
+ if (action != MotionEvent.ACTION_DOWN) {
+ if (mIsDragging) return true;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ final boolean allowDrag = onDownAllowDrag(ev);
+
+ if (allowDrag) {
+ setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
+ stopAnimation();
+ endPeek();
+ mIsDragging = false;
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ final float x = ev.getX();
+ final float dx = x - mLastMotionX;
+ final float xDiff = Math.abs(dx);
+ final float y = ev.getY();
+ final float yDiff = Math.abs(y - mLastMotionY);
+
+ if (xDiff > mTouchSlop && xDiff > yDiff) {
+ if (mOnInterceptMoveEventListener != null && mTouchMode == TOUCH_MODE_FULLSCREEN
+ && canChildScrollHorizontally(mContentContainer, false, (int) dx, (int) x, (int) y)) {
+ endDrag(); // Release the velocity tracker
+ return false;
+ }
+
+ final boolean allowDrag = onMoveAllowDrag(ev, dx);
+
+ if (allowDrag) {
+ setDrawerState(STATE_DRAGGING);
+ mIsDragging = true;
+ mLastMotionX = x;
+ mLastMotionY = y;
+ }
+ }
+ break;
+ }
+
+ /**
+ * If you click really fast, an up or cancel event is delivered here.
+ * Just snap content to whatever is closest.
+ * */
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
+ openMenu();
+ } else {
+ closeMenu();
+ }
+ break;
+ }
+ }
+
+ if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(ev);
+
+ return mIsDragging;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mMenuVisible && !mIsDragging && (mTouchMode == TOUCH_MODE_NONE)) {
+ return false;
+ }
+ final int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(ev);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ final boolean allowDrag = onDownAllowDrag(ev);
+
+ if (allowDrag) {
+ stopAnimation();
+ endPeek();
+ startLayerTranslation();
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ if (!mIsDragging) {
+ final float x = ev.getX();
+ final float dx = x - mLastMotionX;
+ final float xDiff = Math.abs(dx);
+ final float y = ev.getY();
+ final float yDiff = Math.abs(y - mLastMotionY);
+
+ if (xDiff > mTouchSlop && xDiff > yDiff) {
+ final boolean allowDrag = onMoveAllowDrag(ev, dx);
+
+ if (allowDrag) {
+ setDrawerState(STATE_DRAGGING);
+ mIsDragging = true;
+ mLastMotionX = x - mInitialMotionX > 0
+ ? mInitialMotionX + mTouchSlop
+ : mInitialMotionX - mTouchSlop;
+ }
+ }
+ }
+
+ if (mIsDragging) {
+ startLayerTranslation();
+
+ final float x = ev.getX();
+ final float dx = x - mLastMotionX;
+
+ mLastMotionX = x;
+ onMoveEvent(dx);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ onUpEvent(ev);
+ break;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/LeftDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/LeftDrawer.java
new file mode 100644
index 0000000000..59a4fc4ce2
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/LeftDrawer.java
@@ -0,0 +1,212 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+public class LeftDrawer extends HorizontalDrawer {
+
+ private int mIndicatorTop;
+
+ LeftDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public LeftDrawer(Context context) {
+ super(context);
+ }
+
+ public LeftDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LeftDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void openMenu(boolean animate) {
+ animateOffsetTo(mMenuSize, 0, animate);
+ }
+
+ @Override
+ public void closeMenu(boolean animate) {
+ animateOffsetTo(0, 0, animate);
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int width = r - l;
+ final int height = b - t;
+ final int offsetPixels = (int) mOffsetPixels;
+
+ mMenuContainer.layout(0, 0, mMenuSize, height);
+ offsetMenu(offsetPixels);
+
+ if (USE_TRANSLATIONS) {
+ mContentContainer.layout(0, 0, width, height);
+ } else {
+ mContentContainer.layout(offsetPixels, 0, width + offsetPixels, height);
+ }
+ }
+
+ /**
+ * Offsets the menu relative to its original position based on the position of the content.
+ *
+ * @param offsetPixels The number of pixels the content if offset.
+ */
+ private void offsetMenu(int offsetPixels) {
+ if (mOffsetMenu && mMenuSize != 0) {
+ final int menuWidth = mMenuSize;
+ final float openRatio = (menuWidth - (float) offsetPixels) / menuWidth;
+
+ if (USE_TRANSLATIONS) {
+ if (offsetPixels > 0) {
+ final int menuLeft = (int) (0.25f * (-openRatio * menuWidth));
+ mMenuContainer.setTranslationX(menuLeft);
+ } else {
+ mMenuContainer.setTranslationX(-menuWidth);
+ }
+
+ } else {
+ final int oldMenuLeft = mMenuContainer.getLeft();
+ final int offset = (int) (0.25f * (-openRatio * menuWidth)) - oldMenuLeft;
+ mMenuContainer.offsetLeftAndRight(offset);
+ mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ protected void drawDropShadow(Canvas canvas, int offsetPixels) {
+ final int height = getHeight();
+
+ mDropShadowDrawable.setBounds(offsetPixels - mDropShadowSize, 0, offsetPixels, height);
+ mDropShadowDrawable.draw(canvas);
+ }
+
+ @Override
+ protected void drawMenuOverlay(Canvas canvas, int offsetPixels) {
+ final int height = getHeight();
+ final float openRatio = ((float) offsetPixels) / mMenuSize;
+
+ mMenuOverlay.setBounds(0, 0, offsetPixels, height);
+ mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio)));
+ mMenuOverlay.draw(canvas);
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas, int offsetPixels) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ final float openRatio = ((float) offsetPixels) / mMenuSize;
+
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+
+ final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio));
+ final int interpolatedWidth = (int) (mActiveIndicator.getWidth() * interpolatedRatio);
+
+ if (mIndicatorAnimating) {
+ final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height()
+ - mActiveIndicator.getHeight()) / 2);
+ final int indicatorStartTop = mIndicatorStartPos;
+ final int diff = indicatorFinalTop - indicatorStartTop;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorTop = indicatorStartTop + startOffset;
+ } else {
+ mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2);
+ }
+ final int right = offsetPixels;
+ final int left = right - interpolatedWidth;
+
+ canvas.save();
+ canvas.clipRect(left, 0, right, getHeight());
+ canvas.drawBitmap(mActiveIndicator, left, mIndicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorTop;
+ }
+
+ @Override
+ protected void initPeekScroller() {
+ final int dx = mMenuSize / 3;
+ mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
+ }
+
+ @Override
+ protected void onOffsetPixelsChanged(int offsetPixels) {
+ if (USE_TRANSLATIONS) {
+ mContentContainer.setTranslationX(offsetPixels);
+ offsetMenu(offsetPixels);
+ invalidate();
+ } else {
+ mContentContainer.offsetLeftAndRight(offsetPixels - mContentContainer.getLeft());
+ offsetMenu(offsetPixels);
+ invalidate();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // Touch handling
+ //////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected boolean isContentTouch(MotionEvent ev) {
+ return ev.getX() > mOffsetPixels;
+ }
+
+ @Override
+ protected boolean onDownAllowDrag(MotionEvent ev) {
+ return (!mMenuVisible && mInitialMotionX <= mTouchSize)
+ || (mMenuVisible && mInitialMotionX >= mOffsetPixels);
+ }
+
+ @Override
+ protected boolean onMoveAllowDrag(MotionEvent ev, float diff) {
+ return (!mMenuVisible && mInitialMotionX <= mTouchSize && (diff > 0))
+ || (mMenuVisible && mInitialMotionX >= mOffsetPixels);
+ }
+
+ @Override
+ protected void onMoveEvent(float dx) {
+ setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
+ }
+
+ @Override
+ protected void onUpEvent(MotionEvent ev) {
+ final int offsetPixels = (int) mOffsetPixels;
+
+ if (mIsDragging) {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ final int initialVelocity = (int) mVelocityTracker.getXVelocity();
+ mLastMotionX = ev.getX();
+ animateOffsetTo(mVelocityTracker.getXVelocity() > 0 ? mMenuSize : 0, initialVelocity, true);
+
+ // Close the menu when content is clicked while the menu is visible.
+ } else if (mMenuVisible && ev.getX() > offsetPixels) {
+ closeMenu();
+ }
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/LeftStaticDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/LeftStaticDrawer.java
new file mode 100644
index 0000000000..47fa75a356
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/LeftStaticDrawer.java
@@ -0,0 +1,80 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+
+public class LeftStaticDrawer extends StaticDrawer {
+
+ private int mIndicatorTop;
+
+ LeftStaticDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public LeftStaticDrawer(Context context) {
+ super(context);
+ }
+
+ public LeftStaticDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LeftStaticDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super.initDrawer(context, attrs, defStyle);
+ mPosition = Position.LEFT;
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+
+ if (mIndicatorAnimating) {
+ final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height()
+ - mActiveIndicator.getHeight()) / 2);
+ final int indicatorStartTop = mIndicatorStartPos;
+ final int diff = indicatorFinalTop - indicatorStartTop;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorTop = indicatorStartTop + startOffset;
+ } else {
+ mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2);
+ }
+ final int right = mMenuSize;
+ final int left = right - mActiveIndicator.getWidth();
+
+ canvas.save();
+ canvas.clipRect(left, 0, right, getHeight());
+ canvas.drawBitmap(mActiveIndicator, left, mIndicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorTop;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/MenuDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/MenuDrawer.java
new file mode 100644
index 0000000000..7f998e44b4
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/MenuDrawer.java
@@ -0,0 +1,1151 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+
+public abstract class MenuDrawer extends ViewGroup {
+
+ /**
+ * Callback interface for changing state of the drawer.
+ */
+ public interface OnDrawerStateChangeListener {
+
+ /**
+ * Called when the drawer state changes.
+ *
+ * @param oldState The old drawer state.
+ * @param newState The new drawer state.
+ */
+ void onDrawerStateChange(int oldState, int newState);
+ }
+
+ /**
+ * Callback that is invoked when the drawer is in the process of deciding whether it should intercept the touch
+ * event. This lets the listener decide if the pointer is on a view that would disallow dragging of the drawer.
+ * This is only called when the touch mode is {@link #TOUCH_MODE_FULLSCREEN}.
+ */
+ public interface OnInterceptMoveEventListener {
+
+ /**
+ * Called for each child the pointer i on when the drawer is deciding whether to intercept the touch event.
+ *
+ * @param v View to test for draggability
+ * @param dx Delta drag in pixels
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if view is draggable by delta dx.
+ */
+ boolean isViewDraggable(View v, int dx, int x, int y);
+ }
+
+ /**
+ * Tag used when logging.
+ */
+ private static final String TAG = "MenuDrawer";
+
+ /**
+ * Indicates whether debug code should be enabled.
+ */
+ private static final boolean DEBUG = false;
+
+ /**
+ * The time between each frame when animating the drawer.
+ */
+ protected static final int ANIMATION_DELAY = 1000 / 60;
+
+ /**
+ * The default touch bezel size of the drawer in dp.
+ */
+ private static final int DEFAULT_DRAG_BEZEL_DP = 24;
+
+ /**
+ * The default drop shadow size in dp.
+ */
+ private static final int DEFAULT_DROP_SHADOW_DP = 6;
+
+ /**
+ * Drag mode for sliding only the content view.
+ */
+ public static final int MENU_DRAG_CONTENT = 0;
+
+ /**
+ * Drag mode for sliding the entire window.
+ */
+ public static final int MENU_DRAG_WINDOW = 1;
+
+ /**
+ * Disallow opening the drawer by dragging the screen.
+ */
+ public static final int TOUCH_MODE_NONE = 0;
+
+ /**
+ * Allow opening drawer only by dragging on the edge of the screen.
+ */
+ public static final int TOUCH_MODE_BEZEL = 1;
+
+ /**
+ * Allow opening drawer by dragging anywhere on the screen.
+ */
+ public static final int TOUCH_MODE_FULLSCREEN = 2;
+
+ /**
+ * Indicates that the drawer is currently closed.
+ */
+ public static final int STATE_CLOSED = 0;
+
+ /**
+ * Indicates that the drawer is currently closing.
+ */
+ public static final int STATE_CLOSING = 1;
+
+ /**
+ * Indicates that the drawer is currently being dragged by the user.
+ */
+ public static final int STATE_DRAGGING = 2;
+
+ /**
+ * Indicates that the drawer is currently opening.
+ */
+ public static final int STATE_OPENING = 4;
+
+ /**
+ * Indicates that the drawer is currently open.
+ */
+ public static final int STATE_OPEN = 8;
+
+ /**
+ * Indicates whether to use {@link View#setTranslationX(float)} when positioning views.
+ */
+ static final boolean USE_TRANSLATIONS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
+
+ /**
+ * Time to animate the indicator to the new active view.
+ */
+ static final int INDICATOR_ANIM_DURATION = 800;
+
+ /**
+ * The maximum animation duration.
+ */
+ private static final int DEFAULT_ANIMATION_DURATION = 600;
+
+ /**
+ * Interpolator used when animating the drawer open/closed.
+ */
+ protected static final Interpolator SMOOTH_INTERPOLATOR = new SmoothInterpolator();
+
+ /**
+ * Drawable used as menu overlay.
+ */
+ protected Drawable mMenuOverlay;
+
+ /**
+ * Defines whether the drop shadow is enabled.
+ */
+ protected boolean mDropShadowEnabled;
+
+ /**
+ * Drawable used as content drop shadow onto the menu.
+ */
+ protected Drawable mDropShadowDrawable;
+
+ /**
+ * The size of the content drop shadow.
+ */
+ protected int mDropShadowSize;
+
+ /**
+ * Bitmap used to indicate the active view.
+ */
+ protected Bitmap mActiveIndicator;
+
+ /**
+ * The currently active view.
+ */
+ protected View mActiveView;
+
+ /**
+ * Position of the active view. This is compared to View#getTag(R.id.mdActiveViewPosition) when drawing the
+ * indicator.
+ */
+ protected int mActivePosition;
+
+ /**
+ * Whether the indicator should be animated between positions.
+ */
+ private boolean mAllowIndicatorAnimation;
+
+ /**
+ * Used when reading the position of the active view.
+ */
+ protected final Rect mActiveRect = new Rect();
+
+ /**
+ * Temporary {@link Rect} used for deciding whether the view should be invalidated so the indicator can be redrawn.
+ */
+ private final Rect mTempRect = new Rect();
+
+ /**
+ * The custom menu view set by the user.
+ */
+ private View mMenuView;
+
+ /**
+ * The parent of the menu view.
+ */
+ protected BuildLayerFrameLayout mMenuContainer;
+
+ /**
+ * The parent of the content view.
+ */
+ protected BuildLayerFrameLayout mContentContainer;
+
+ /**
+ * The size of the menu (width or height depending on the gravity).
+ */
+ protected int mMenuSize;
+
+ /**
+ * Indicates whether the menu size has been set explicity either via the theme or by calling
+ * {@link #setMenuSize(int)}.
+ */
+ protected boolean mMenuSizeSet;
+
+ /**
+ * Indicates whether the menu is currently visible.
+ */
+ protected boolean mMenuVisible;
+
+ /**
+ * The drag mode of the drawer. Can be either {@link #MENU_DRAG_CONTENT} or {@link #MENU_DRAG_WINDOW}.
+ */
+ private int mDragMode = MENU_DRAG_CONTENT;
+
+ /**
+ * The current drawer state.
+ *
+ * @see #STATE_CLOSED
+ * @see #STATE_CLOSING
+ * @see #STATE_DRAGGING
+ * @see #STATE_OPENING
+ * @see #STATE_OPEN
+ */
+ protected int mDrawerState = STATE_CLOSED;
+
+ /**
+ * The touch bezel size of the drawer in px.
+ */
+ protected int mTouchBezelSize;
+
+ /**
+ * The touch area size of the drawer in px.
+ */
+ protected int mTouchSize;
+
+ /**
+ * Listener used to dispatch state change events.
+ */
+ private OnDrawerStateChangeListener mOnDrawerStateChangeListener;
+
+ /**
+ * Touch mode for the Drawer.
+ * Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or {@link #TOUCH_MODE_FULLSCREEN}
+ * Default: {@link #TOUCH_MODE_BEZEL}
+ */
+ protected int mTouchMode = TOUCH_MODE_BEZEL;
+
+ /**
+ * Indicates whether to use {@link View#LAYER_TYPE_HARDWARE} when animating the drawer.
+ */
+ protected boolean mHardwareLayersEnabled = true;
+
+ /**
+ * The Activity the drawer is attached to.
+ */
+ private Activity mActivity;
+
+ /**
+ * Scroller used when animating the indicator to a new position.
+ */
+ private FloatScroller mIndicatorScroller;
+
+ /**
+ * Runnable used when animating the indicator to a new position.
+ */
+ private Runnable mIndicatorRunnable = new Runnable() {
+ @Override
+ public void run() {
+ animateIndicatorInvalidate();
+ }
+ };
+
+ /**
+ * The start position of the indicator when animating it to a new position.
+ */
+ protected int mIndicatorStartPos;
+
+ /**
+ * [0..1] value indicating the current progress of the animation.
+ */
+ protected float mIndicatorOffset;
+
+ /**
+ * Whether the indicator is currently animating.
+ */
+ protected boolean mIndicatorAnimating;
+
+ /**
+ * Bundle used to hold the drawers state.
+ */
+ protected Bundle mState;
+
+ /**
+ * The maximum duration of open/close animations.
+ */
+ protected int mMaxAnimationDuration = DEFAULT_ANIMATION_DURATION;
+
+ /**
+ * Callback that lets the listener override intercepting of touch events.
+ */
+ protected OnInterceptMoveEventListener mOnInterceptMoveEventListener;
+
+ /**
+ * Attaches the MenuDrawer to the Activity.
+ *
+ * @param activity The activity that the MenuDrawer will be attached to.
+ * @return The created MenuDrawer instance.
+ */
+ public static MenuDrawer attach(Activity activity) {
+ return attach(activity, MENU_DRAG_CONTENT);
+ }
+
+ /**
+ * Attaches the MenuDrawer to the Activity.
+ *
+ * @param activity The activity the menu drawer will be attached to.
+ * @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
+ * or {@link MenuDrawer#MENU_DRAG_WINDOW}.
+ * @return The created MenuDrawer instance.
+ */
+ public static MenuDrawer attach(Activity activity, int dragMode) {
+ return attach(activity, dragMode, Position.LEFT);
+ }
+
+ /**
+ * Attaches the MenuDrawer to the Activity.
+ *
+ * @param activity The activity the menu drawer will be attached to.
+ * @param position Where to position the menu.
+ * @return The created MenuDrawer instance.
+ */
+ public static MenuDrawer attach(Activity activity, Position position) {
+ return attach(activity, MENU_DRAG_CONTENT, position);
+ }
+
+ /**
+ * Attaches the MenuDrawer to the Activity.
+ *
+ * @param activity The activity the menu drawer will be attached to.
+ * @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
+ * or {@link MenuDrawer#MENU_DRAG_WINDOW}.
+ * @param position Where to position the menu.
+ * @return The created MenuDrawer instance.
+ */
+ public static MenuDrawer attach(Activity activity, int dragMode, Position position) {
+ return attach(activity, dragMode, position, false);
+ }
+
+ /**
+ * Attaches the MenuDrawer to the Activity.
+ *
+ * @param activity The activity the menu drawer will be attached to.
+ * @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
+ * or {@link MenuDrawer#MENU_DRAG_WINDOW}.
+ * @param position Where to position the menu.
+ * @param attachStatic Whether a static (non-draggable, always visible) drawer should be used.
+ * @return The created MenuDrawer instance.
+ */
+ public static MenuDrawer attach(Activity activity, int dragMode, Position position, boolean attachStatic) {
+ MenuDrawer menuDrawer = createMenuDrawer(activity, dragMode, position, attachStatic);
+ menuDrawer.setId(R.id.md__drawer);
+
+ switch (dragMode) {
+ case MenuDrawer.MENU_DRAG_CONTENT:
+ attachToContent(activity, menuDrawer);
+ break;
+
+ case MenuDrawer.MENU_DRAG_WINDOW:
+ attachToDecor(activity, menuDrawer);
+ break;
+
+ default:
+ throw new RuntimeException("Unknown menu mode: " + dragMode);
+ }
+
+ return menuDrawer;
+ }
+
+ /**
+ * Constructs the appropriate MenuDrawer based on the position.
+ */
+ private static MenuDrawer createMenuDrawer(Activity activity, int dragMode, Position position,
+ boolean attachStatic) {
+ if (attachStatic) {
+ switch (position) {
+ case LEFT:
+ return new LeftStaticDrawer(activity, dragMode);
+ case RIGHT:
+ return new RightStaticDrawer(activity, dragMode);
+ case TOP:
+ return new TopStaticDrawer(activity, dragMode);
+ case BOTTOM:
+ return new BottomStaticDrawer(activity, dragMode);
+ default:
+ throw new IllegalArgumentException("position must be one of LEFT, TOP, RIGHT or BOTTOM");
+ }
+ }
+
+ switch (position) {
+ case LEFT:
+ return new LeftDrawer(activity, dragMode);
+ case RIGHT:
+ return new RightDrawer(activity, dragMode);
+ case TOP:
+ return new TopDrawer(activity, dragMode);
+ case BOTTOM:
+ return new BottomDrawer(activity, dragMode);
+ default:
+ throw new IllegalArgumentException("position must be one of LEFT, TOP, RIGHT or BOTTOM");
+ }
+ }
+
+ /**
+ * Attaches the menu drawer to the content view.
+ */
+ private static void attachToContent(Activity activity, MenuDrawer menuDrawer) {
+ /**
+ * Do not call mActivity#setContentView.
+ * E.g. if using with a ListActivity, Activity#setContentView is overridden and dispatched to
+ * MenuDrawer#setContentView, which then again would call Activity#setContentView.
+ */
+ ViewGroup content = (ViewGroup) activity.findViewById(android.R.id.content);
+ content.removeAllViews();
+ content.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ /**
+ * Attaches the menu drawer to the window.
+ */
+ private static void attachToDecor(Activity activity, MenuDrawer menuDrawer) {
+ ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
+ ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);
+
+ decorView.removeAllViews();
+ decorView.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+
+ menuDrawer.mContentContainer.addView(decorChild, decorChild.getLayoutParams());
+ }
+
+ MenuDrawer(Activity activity, int dragMode) {
+ this(activity);
+
+ mActivity = activity;
+ mDragMode = dragMode;
+ }
+
+ public MenuDrawer(Context context) {
+ this(context, null);
+ }
+
+ public MenuDrawer(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.menuDrawerStyle);
+ }
+
+ public MenuDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initDrawer(context, attrs, defStyle);
+ }
+
+ protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
+ setWillNotDraw(false);
+ setFocusable(false);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MenuDrawer, R.attr.menuDrawerStyle,
+ R.style.Widget_MenuDrawer);
+
+ final Drawable contentBackground = a.getDrawable(R.styleable.MenuDrawer_mdContentBackground);
+ final Drawable menuBackground = a.getDrawable(R.styleable.MenuDrawer_mdMenuBackground);
+
+ mMenuSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdMenuSize, -1);
+ mMenuSizeSet = mMenuSize != -1;
+
+ final int indicatorResId = a.getResourceId(R.styleable.MenuDrawer_mdActiveIndicator, 0);
+ if (indicatorResId != 0) {
+ mActiveIndicator = BitmapFactory.decodeResource(getResources(), indicatorResId);
+ }
+
+ mDropShadowEnabled = a.getBoolean(R.styleable.MenuDrawer_mdDropShadowEnabled, true);
+
+ mDropShadowDrawable = a.getDrawable(R.styleable.MenuDrawer_mdDropShadow);
+
+ if (mDropShadowDrawable == null) {
+ final int dropShadowColor = a.getColor(R.styleable.MenuDrawer_mdDropShadowColor, 0xFF000000);
+ setDropShadowColor(dropShadowColor);
+ }
+
+ mDropShadowSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdDropShadowSize,
+ dpToPx(DEFAULT_DROP_SHADOW_DP));
+
+ mTouchBezelSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdTouchBezelSize,
+ dpToPx(DEFAULT_DRAG_BEZEL_DP));
+
+ mAllowIndicatorAnimation = a.getBoolean(R.styleable.MenuDrawer_mdAllowIndicatorAnimation, false);
+
+ mMaxAnimationDuration = a.getInt(R.styleable.MenuDrawer_mdMaxAnimationDuration, DEFAULT_ANIMATION_DURATION);
+
+ a.recycle();
+
+ mMenuContainer = new BuildLayerFrameLayout(context);
+ mMenuContainer.setId(R.id.md__menu);
+ mMenuContainer.setBackgroundDrawable(menuBackground);
+ super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ mContentContainer = new NoClickThroughFrameLayout(context);
+ mContentContainer.setId(R.id.md__content);
+ mContentContainer.setBackgroundDrawable(contentBackground);
+ super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ mMenuOverlay = new ColorDrawable(0xFF000000);
+
+ mIndicatorScroller = new FloatScroller(SMOOTH_INTERPOLATOR);
+ }
+
+ @Override
+ public void addView(View child, int index, LayoutParams params) {
+ int childCount = mMenuContainer.getChildCount();
+ if (childCount == 0) {
+ mMenuContainer.addView(child, index, params);
+ return;
+ }
+
+ childCount = mContentContainer.getChildCount();
+ if (childCount == 0) {
+ mContentContainer.addView(child, index, params);
+ return;
+ }
+
+ throw new IllegalStateException("MenuDrawer can only hold two child views");
+ }
+
+ protected int dpToPx(int dp) {
+ return (int) (getResources().getDisplayMetrics().density * dp + 0.5f);
+ }
+
+ protected boolean isViewDescendant(View v) {
+ ViewParent parent = v.getParent();
+ while (parent != null) {
+ if (parent == this) {
+ return true;
+ }
+
+ parent = parent.getParent();
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnScrollChangedListener(mScrollListener);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ getViewTreeObserver().removeOnScrollChangedListener(mScrollListener);
+ super.onDetachedFromWindow();
+ }
+
+ /**
+ * Toggles the menu open and close with animation.
+ */
+ public void toggleMenu() {
+ toggleMenu(true);
+ }
+
+ /**
+ * Toggles the menu open and close.
+ *
+ * @param animate Whether open/close should be animated.
+ */
+ public abstract void toggleMenu(boolean animate);
+
+ /**
+ * Animates the menu open.
+ */
+ public void openMenu() {
+ openMenu(true);
+ }
+
+ /**
+ * Opens the menu.
+ *
+ * @param animate Whether open/close should be animated.
+ */
+ public abstract void openMenu(boolean animate);
+
+ /**
+ * Animates the menu closed.
+ */
+ public void closeMenu() {
+ closeMenu(true);
+ }
+
+ /**
+ * Closes the menu.
+ *
+ * @param animate Whether open/close should be animated.
+ */
+ public abstract void closeMenu(boolean animate);
+
+ /**
+ * Indicates whether the menu is currently visible.
+ *
+ * @return True if the menu is open, false otherwise.
+ */
+ public abstract boolean isMenuVisible();
+
+ /**
+ * Set the size of the menu drawer when open.
+ *
+ * @param size The size of the menu.
+ */
+ public abstract void setMenuSize(int size);
+
+ /**
+ * Returns the size of the menu.
+ *
+ * @return The size of the menu.
+ */
+ public int getMenuSize() {
+ return mMenuSize;
+ }
+
+ /**
+ * Set the active view.
+ * If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
+ *
+ * @param v The active view.
+ */
+ public void setActiveView(View v) {
+ setActiveView(v, 0);
+ }
+
+ /**
+ * Set the active view.
+ * If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
+ *
+ * @param v The active view.
+ * @param position Optional position, usually used with ListView. v.setTag(R.id.mdActiveViewPosition, position)
+ * must be called first.
+ */
+ public void setActiveView(View v, int position) {
+ final View oldView = mActiveView;
+ mActiveView = v;
+ mActivePosition = position;
+
+ if (mAllowIndicatorAnimation && oldView != null) {
+ startAnimatingIndicator();
+ }
+
+ invalidate();
+ }
+
+ /**
+ * Sets whether the indicator should be animated between active views.
+ *
+ * @param animate Whether the indicator should be animated between active views.
+ */
+ public void setAllowIndicatorAnimation(boolean animate) {
+ if (animate != mAllowIndicatorAnimation) {
+ mAllowIndicatorAnimation = animate;
+ completeAnimatingIndicator();
+ }
+ }
+
+ /**
+ * Indicates whether the indicator should be animated between active views.
+ *
+ * @return Whether the indicator should be animated between active views.
+ */
+ public boolean getAllowIndicatorAnimation() {
+ return mAllowIndicatorAnimation;
+ }
+
+ /**
+ * Scroll listener that checks whether the active view has moved before the drawer is invalidated.
+ */
+ private ViewTreeObserver.OnScrollChangedListener mScrollListener = new ViewTreeObserver.OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ mActiveView.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(mActiveView, mTempRect);
+ if (mTempRect.left != mActiveRect.left || mTempRect.top != mActiveRect.top
+ || mTempRect.right != mActiveRect.right || mTempRect.bottom != mActiveRect.bottom) {
+ invalidate();
+ }
+ }
+ }
+ };
+
+ /**
+ * Starts animating the indicator to a new position.
+ */
+ private void startAnimatingIndicator() {
+ mIndicatorStartPos = getIndicatorStartPos();
+ mIndicatorAnimating = true;
+ mIndicatorScroller.startScroll(0.0f, 1.0f, INDICATOR_ANIM_DURATION);
+
+ animateIndicatorInvalidate();
+ }
+
+ /**
+ * Returns the start position of the indicator.
+ *
+ * @return The start position of the indicator.
+ */
+ protected abstract int getIndicatorStartPos();
+
+ /**
+ * Callback when each frame in the indicator animation should be drawn.
+ */
+ private void animateIndicatorInvalidate() {
+ if (mIndicatorScroller.computeScrollOffset()) {
+ mIndicatorOffset = mIndicatorScroller.getCurr();
+ invalidate();
+
+ if (!mIndicatorScroller.isFinished()) {
+ postOnAnimation(mIndicatorRunnable);
+ return;
+ }
+ }
+
+ completeAnimatingIndicator();
+ }
+
+ /**
+ * Called when the indicator animation has completed.
+ */
+ private void completeAnimatingIndicator() {
+ mIndicatorOffset = 1.0f;
+ mIndicatorAnimating = false;
+ invalidate();
+ }
+
+ /**
+ * Enables or disables offsetting the menu when dragging the drawer.
+ *
+ * @param offsetMenu True to offset the menu, false otherwise.
+ */
+ public abstract void setOffsetMenuEnabled(boolean offsetMenu);
+
+ /**
+ * Indicates whether the menu is being offset when dragging the drawer.
+ *
+ * @return True if the menu is being offset, false otherwise.
+ */
+ public abstract boolean getOffsetMenuEnabled();
+
+ public int getDrawerState() {
+ return mDrawerState;
+ }
+
+ /**
+ * Register a callback to be invoked when the drawer state changes.
+ *
+ * @param listener The callback that will run.
+ */
+ public void setOnDrawerStateChangeListener(OnDrawerStateChangeListener listener) {
+ mOnDrawerStateChangeListener = listener;
+ }
+
+ /**
+ * Register a callback that will be invoked when the drawer is about to intercept touch events.
+ *
+ * @param listener The callback that will be invoked.
+ */
+ public void setOnInterceptMoveEventListener(OnInterceptMoveEventListener listener) {
+ mOnInterceptMoveEventListener = listener;
+ }
+
+ /**
+ * Defines whether the drop shadow is enabled.
+ *
+ * @param enabled Whether the drop shadow is enabled.
+ */
+ public void setDropShadowEnabled(boolean enabled) {
+ mDropShadowEnabled = enabled;
+ invalidate();
+ }
+
+ /**
+ * Sets the color of the drop shadow.
+ *
+ * @param color The color of the drop shadow.
+ */
+ public abstract void setDropShadowColor(int color);
+
+ /**
+ * Sets the drawable of the drop shadow.
+ *
+ * @param drawable The drawable of the drop shadow.
+ */
+ public void setDropShadow(Drawable drawable) {
+ mDropShadowDrawable = drawable;
+ invalidate();
+ }
+
+ /**
+ * Sets the drawable of the drop shadow.
+ *
+ * @param resId The resource identifier of the the drawable.
+ */
+ public void setDropShadow(int resId) {
+ setDropShadow(getResources().getDrawable(resId));
+ }
+
+ /**
+ * Returns the drawable of the drop shadow.
+ */
+ public Drawable getDropShadow() {
+ return mDropShadowDrawable;
+ }
+
+ /**
+ * Sets the size of the drop shadow.
+ *
+ * @param size The size of the drop shadow in px.
+ */
+ public void setDropShadowSize(int size) {
+ mDropShadowSize = size;
+ invalidate();
+ }
+
+ /**
+ * Animates the drawer slightly open until the user opens the drawer.
+ */
+ public abstract void peekDrawer();
+
+ /**
+ * Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the drawer.
+ *
+ * @param delay The delay (in milliseconds) between each run of the animation. If 0, this animation is only run
+ * once.
+ */
+ public abstract void peekDrawer(long delay);
+
+ /**
+ * Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the drawer.
+ *
+ * @param startDelay The delay (in milliseconds) until the animation is first run.
+ * @param delay The delay (in milliseconds) between each run of the animation. If 0, this animation is only run
+ * once.
+ */
+ public abstract void peekDrawer(long startDelay, long delay);
+
+ /**
+ * Enables or disables the user of {@link View#LAYER_TYPE_HARDWARE} when animations views.
+ *
+ * @param enabled Whether hardware layers are enabled.
+ */
+ public abstract void setHardwareLayerEnabled(boolean enabled);
+
+ /**
+ * Sets the maximum duration of open/close animations.
+ * @param duration The maximum duration in milliseconds.
+ */
+ public void setMaxAnimationDuration(int duration) {
+ mMaxAnimationDuration = duration;
+ }
+
+ /**
+ * Returns the ViewGroup used as a parent for the menu view.
+ *
+ * @return The menu view's parent.
+ */
+ public ViewGroup getMenuContainer() {
+ return mMenuContainer;
+ }
+
+ /**
+ * Returns the ViewGroup used as a parent for the content view.
+ *
+ * @return The content view's parent.
+ */
+ public ViewGroup getContentContainer() {
+ if (mDragMode == MENU_DRAG_CONTENT) {
+ return mContentContainer;
+ } else {
+ return (ViewGroup) findViewById(android.R.id.content);
+ }
+ }
+
+ /**
+ * Set the menu view from a layout resource.
+ *
+ * @param layoutResId Resource ID to be inflated.
+ */
+ public void setMenuView(int layoutResId) {
+ mMenuContainer.removeAllViews();
+ mMenuView = LayoutInflater.from(getContext()).inflate(layoutResId, mMenuContainer, false);
+ mMenuContainer.addView(mMenuView);
+ }
+
+ /**
+ * Set the menu view to an explicit view.
+ *
+ * @param view The menu view.
+ */
+ public void setMenuView(View view) {
+ setMenuView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ }
+
+ /**
+ * Set the menu view to an explicit view.
+ *
+ * @param view The menu view.
+ * @param params Layout parameters for the view.
+ */
+ public void setMenuView(View view, LayoutParams params) {
+ mMenuView = view;
+ mMenuContainer.removeAllViews();
+ mMenuContainer.addView(view, params);
+ }
+
+ /**
+ * Returns the menu view.
+ *
+ * @return The menu view.
+ */
+ public View getMenuView() {
+ return mMenuView;
+ }
+
+ /**
+ * Set the content from a layout resource.
+ *
+ * @param layoutResId Resource ID to be inflated.
+ */
+ public void setContentView(int layoutResId) {
+ switch (mDragMode) {
+ case MenuDrawer.MENU_DRAG_CONTENT:
+ mContentContainer.removeAllViews();
+ LayoutInflater.from(getContext()).inflate(layoutResId, mContentContainer, true);
+ break;
+
+ case MenuDrawer.MENU_DRAG_WINDOW:
+ mActivity.setContentView(layoutResId);
+ break;
+ }
+ }
+
+ /**
+ * Set the content to an explicit view.
+ *
+ * @param view The desired content to display.
+ */
+ public void setContentView(View view) {
+ setContentView(view, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ }
+
+ /**
+ * Set the content to an explicit view.
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ */
+ public void setContentView(View view, LayoutParams params) {
+ switch (mDragMode) {
+ case MenuDrawer.MENU_DRAG_CONTENT:
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(view, params);
+ break;
+
+ case MenuDrawer.MENU_DRAG_WINDOW:
+ mActivity.setContentView(view, params);
+ break;
+ }
+ }
+
+ protected void setDrawerState(int state) {
+ if (state != mDrawerState) {
+ final int oldState = mDrawerState;
+ mDrawerState = state;
+ if (mOnDrawerStateChangeListener != null) mOnDrawerStateChangeListener.onDrawerStateChange(oldState, state);
+ if (DEBUG) logDrawerState(state);
+ }
+ }
+
+ protected void logDrawerState(int state) {
+ switch (state) {
+ case STATE_CLOSED:
+ Log.d(TAG, "[DrawerState] STATE_CLOSED");
+ break;
+
+ case STATE_CLOSING:
+ Log.d(TAG, "[DrawerState] STATE_CLOSING");
+ break;
+
+ case STATE_DRAGGING:
+ Log.d(TAG, "[DrawerState] STATE_DRAGGING");
+ break;
+
+ case STATE_OPENING:
+ Log.d(TAG, "[DrawerState] STATE_OPENING");
+ break;
+
+ case STATE_OPEN:
+ Log.d(TAG, "[DrawerState] STATE_OPEN");
+ break;
+
+ default:
+ Log.d(TAG, "[DrawerState] Unknown: " + state);
+ }
+ }
+
+ /**
+ * Returns the touch mode.
+ */
+ public abstract int getTouchMode();
+
+ /**
+ * Sets the drawer touch mode. Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or
+ * {@link #TOUCH_MODE_FULLSCREEN}.
+ *
+ * @param mode The touch mode.
+ */
+ public abstract void setTouchMode(int mode);
+
+ /**
+ * Sets the size of the touch bezel.
+ *
+ * @param size The touch bezel size in px.
+ */
+ public abstract void setTouchBezelSize(int size);
+
+ /**
+ * Returns the size of the touch bezel in px.
+ */
+ public abstract int getTouchBezelSize();
+
+ @Override
+ public void postOnAnimation(Runnable action) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ super.postOnAnimation(action);
+ } else {
+ postDelayed(action, ANIMATION_DELAY);
+ }
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ if (mDragMode == MENU_DRAG_WINDOW) {
+ mMenuContainer.setPadding(0, insets.top, 0, 0);
+ }
+ return super.fitSystemWindows(insets);
+ }
+
+ /**
+ * Saves the state of the drawer.
+ *
+ * @return Returns a Parcelable containing the drawer state.
+ */
+ public final Parcelable saveState() {
+ if (mState == null) mState = new Bundle();
+ saveState(mState);
+ return mState;
+ }
+
+ void saveState(Bundle state) {
+ // State saving isn't required for subclasses.
+ }
+
+ /**
+ * Restores the state of the drawer.
+ *
+ * @param in A parcelable containing the drawer state.
+ */
+ public void restoreState(Parcelable in) {
+ mState = (Bundle) in;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState state = new SavedState(superState);
+
+ if (mState == null) mState = new Bundle();
+ saveState(mState);
+
+ state.mState = mState;
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState savedState = (SavedState) state;
+ super.onRestoreInstanceState(savedState.getSuperState());
+
+ restoreState(savedState.mState);
+ }
+
+ static class SavedState extends BaseSavedState {
+
+ Bundle mState;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel in) {
+ super(in);
+ mState = in.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeBundle(mState);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/NoClickThroughFrameLayout.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/NoClickThroughFrameLayout.java
new file mode 100644
index 0000000000..9462282cb1
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/NoClickThroughFrameLayout.java
@@ -0,0 +1,28 @@
+package net.simonvt.menudrawer;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+/**
+ * FrameLayout which doesn't let touch events propagate to views positioned behind it in the view hierarchy.
+ */
+public class NoClickThroughFrameLayout extends BuildLayerFrameLayout {
+
+ public NoClickThroughFrameLayout(Context context) {
+ super(context);
+ }
+
+ public NoClickThroughFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NoClickThroughFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return true;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/PeekInterpolator.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/PeekInterpolator.java
new file mode 100644
index 0000000000..d0c7600f66
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/PeekInterpolator.java
@@ -0,0 +1,28 @@
+package net.simonvt.menudrawer;
+
+import android.view.animation.Interpolator;
+
+public class PeekInterpolator implements Interpolator {
+
+ private static final String TAG = "PeekInterpolator";
+
+ private static final SinusoidalInterpolator SINUSOIDAL_INTERPOLATOR = new SinusoidalInterpolator();
+
+ @Override
+ public float getInterpolation(float input) {
+ float result;
+
+ if (input < 1.f / 3.f) {
+ result = SINUSOIDAL_INTERPOLATOR.getInterpolation(input * 3);
+
+ } else if (input > 2.f / 3.f) {
+ final float val = ((input + 1.f / 3.f) - 1.f) * 3.f;
+ result = 1.f - SINUSOIDAL_INTERPOLATOR.getInterpolation(val);
+
+ } else {
+ result = 1.f;
+ }
+
+ return result;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/Position.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/Position.java
new file mode 100644
index 0000000000..e1a3dd3a46
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/Position.java
@@ -0,0 +1,18 @@
+package net.simonvt.menudrawer;
+
+/**
+ * Enums used for positioning the drawer.
+ */
+public enum Position {
+ // Positions the drawer to the left of the content.
+ LEFT,
+
+ // Positions the drawer above the content.
+ TOP,
+
+ // Positions the drawer to the right of the content.
+ RIGHT,
+
+ // Positions the drawer below the content.
+ BOTTOM,
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/RightDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/RightDrawer.java
new file mode 100644
index 0000000000..5d75904aac
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/RightDrawer.java
@@ -0,0 +1,234 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+public class RightDrawer extends HorizontalDrawer {
+
+ private int mIndicatorTop;
+
+ RightDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public RightDrawer(Context context) {
+ super(context);
+ }
+
+ public RightDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RightDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void openMenu(boolean animate) {
+ animateOffsetTo(-mMenuSize, 0, animate);
+ }
+
+ @Override
+ public void closeMenu(boolean animate) {
+ animateOffsetTo(0, 0, animate);
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int width = r - l;
+ final int height = b - t;
+ final int offsetPixels = (int) mOffsetPixels;
+
+ mMenuContainer.layout(width - mMenuSize, 0, width, height);
+ offsetMenu(offsetPixels);
+
+ if (USE_TRANSLATIONS) {
+ mContentContainer.layout(0, 0, width, height);
+ } else {
+ mContentContainer.layout(offsetPixels, 0, width + offsetPixels, height);
+ }
+ }
+
+ /**
+ * Offsets the menu relative to its original position based on the position of the content.
+ *
+ * @param offsetPixels The number of pixels the content if offset.
+ */
+ private void offsetMenu(int offsetPixels) {
+ if (mOffsetMenu && mMenuSize != 0) {
+ final int menuWidth = mMenuSize;
+ final float openRatio = (menuWidth + (float) offsetPixels) / menuWidth;
+
+ if (USE_TRANSLATIONS) {
+ if (offsetPixels != 0) {
+ final int offset = (int) (0.25f * (openRatio * menuWidth));
+ mMenuContainer.setTranslationX(offset);
+ } else {
+ mMenuContainer.setTranslationX(-menuWidth);
+ }
+
+ } else {
+ final int width = getWidth();
+ final int oldMenuRight = mMenuContainer.getRight();
+ final int newRight = width + (int) (0.25f * (openRatio * menuWidth));
+ final int offset = newRight - oldMenuRight;
+ mMenuContainer.offsetLeftAndRight(offset);
+ mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ protected void drawDropShadow(Canvas canvas, int offsetPixels) {
+ final int height = getHeight();
+ final int width = getWidth();
+ final int left = width + offsetPixels;
+ final int right = left + mDropShadowSize;
+
+ mDropShadowDrawable.setBounds(left, 0, right, height);
+ mDropShadowDrawable.draw(canvas);
+ }
+
+ @Override
+ protected void drawMenuOverlay(Canvas canvas, int offsetPixels) {
+ final int height = getHeight();
+ final int width = getWidth();
+ final int left = width + offsetPixels;
+ final int right = width;
+ final float openRatio = ((float) Math.abs(offsetPixels)) / mMenuSize;
+
+ mMenuOverlay.setBounds(left, 0, right, height);
+ mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio)));
+ mMenuOverlay.draw(canvas);
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas, int offsetPixels) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ final int width = getWidth();
+ final int menuWidth = mMenuSize;
+ final int indicatorWidth = mActiveIndicator.getWidth();
+
+ final int contentRight = width + offsetPixels;
+ final float openRatio = ((float) Math.abs(offsetPixels)) / menuWidth;
+
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+
+ final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio));
+ final int interpolatedWidth = (int) (indicatorWidth * interpolatedRatio);
+
+ final int indicatorRight = contentRight + interpolatedWidth;
+ final int indicatorLeft = indicatorRight - indicatorWidth;
+
+ if (mIndicatorAnimating) {
+ final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height()
+ - mActiveIndicator.getHeight()) / 2);
+ final int indicatorStartTop = mIndicatorStartPos;
+ final int diff = indicatorFinalTop - indicatorStartTop;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorTop = indicatorStartTop + startOffset;
+ } else {
+ mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2);
+ }
+
+ canvas.save();
+ canvas.clipRect(contentRight, 0, indicatorRight, getHeight());
+ canvas.drawBitmap(mActiveIndicator, indicatorLeft, mIndicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorTop;
+ }
+
+ @Override
+ protected void initPeekScroller() {
+ final int dx = -mMenuSize / 3;
+ mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
+ }
+
+ @Override
+ protected void onOffsetPixelsChanged(int offsetPixels) {
+ if (USE_TRANSLATIONS) {
+ mContentContainer.setTranslationX(offsetPixels);
+ offsetMenu(offsetPixels);
+ invalidate();
+ } else {
+ mContentContainer.offsetLeftAndRight(offsetPixels - mContentContainer.getLeft());
+ offsetMenu(offsetPixels);
+ invalidate();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // Touch handling
+ //////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected boolean isContentTouch(MotionEvent ev) {
+ return ev.getX() < getWidth() + mOffsetPixels;
+ }
+
+ @Override
+ protected boolean onDownAllowDrag(MotionEvent ev) {
+ final int width = getWidth();
+ final int initialMotionX = (int) mInitialMotionX;
+
+ return (!mMenuVisible && initialMotionX >= width - mTouchSize)
+ || (mMenuVisible && initialMotionX <= width + mOffsetPixels);
+ }
+
+ @Override
+ protected boolean onMoveAllowDrag(MotionEvent ev, float diff) {
+ final int width = getWidth();
+ final int initialMotionX = (int) mInitialMotionX;
+
+ return (!mMenuVisible && initialMotionX >= width - mTouchSize && (diff < 0))
+ || (mMenuVisible && initialMotionX <= width + mOffsetPixels);
+ }
+
+ @Override
+ protected void onMoveEvent(float dx) {
+ final float newOffset = Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize);
+ setOffsetPixels(newOffset);
+ }
+
+ @Override
+ protected void onUpEvent(MotionEvent ev) {
+ final int offsetPixels = (int) mOffsetPixels;
+ final int width = getWidth();
+
+ if (mIsDragging) {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ final int initialVelocity = (int) mVelocityTracker.getXVelocity();
+ mLastMotionX = ev.getX();
+ animateOffsetTo(mVelocityTracker.getXVelocity() > 0 ? 0 : -mMenuSize, initialVelocity, true);
+
+ // Close the menu when content is clicked while the menu is visible.
+ } else if (mMenuVisible && ev.getX() < width + offsetPixels) {
+ closeMenu();
+ }
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/RightStaticDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/RightStaticDrawer.java
new file mode 100644
index 0000000000..2027b43acc
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/RightStaticDrawer.java
@@ -0,0 +1,87 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+
+public class RightStaticDrawer extends StaticDrawer {
+
+ private int mIndicatorTop;
+
+ RightStaticDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public RightStaticDrawer(Context context) {
+ super(context);
+ }
+
+ public RightStaticDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RightStaticDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super.initDrawer(context, attrs, defStyle);
+ mPosition = Position.RIGHT;
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ final int width = getWidth();
+ final int menuWidth = mMenuSize;
+ final int indicatorWidth = mActiveIndicator.getWidth();
+
+ final int contentRight = width - menuWidth;
+
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+
+ final int indicatorRight = contentRight + indicatorWidth;
+ final int indicatorLeft = contentRight;
+
+ if (mIndicatorAnimating) {
+ final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height()
+ - mActiveIndicator.getHeight()) / 2);
+ final int indicatorStartTop = mIndicatorStartPos;
+ final int diff = indicatorFinalTop - indicatorStartTop;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorTop = indicatorStartTop + startOffset;
+ } else {
+ mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2);
+ }
+
+ canvas.save();
+ canvas.clipRect(contentRight, 0, indicatorRight, getHeight());
+ canvas.drawBitmap(mActiveIndicator, indicatorLeft, mIndicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorTop;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/Scroller.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/Scroller.java
new file mode 100644
index 0000000000..58f0fc54e5
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/Scroller.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.simonvt.menudrawer;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.FloatMath;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+
+/**
+ * This class encapsulates scrolling. The duration of the scroll
+ * can be passed in the constructor and specifies the maximum time that
+ * the scrolling animation should take. Past this time, the scrolling is
+ * automatically moved to its final stage and computeScrollOffset()
+ * will always return false to indicate that scrolling is over.
+ */
+public class Scroller {
+ private int mMode;
+
+ private int mStartX;
+ private int mStartY;
+ private int mFinalX;
+ private int mFinalY;
+
+ private int mMinX;
+ private int mMaxX;
+ private int mMinY;
+ private int mMaxY;
+
+ private int mCurrX;
+ private int mCurrY;
+ private long mStartTime;
+ private int mDuration;
+ private float mDurationReciprocal;
+ private float mDeltaX;
+ private float mDeltaY;
+ private boolean mFinished;
+ private Interpolator mInterpolator;
+ private boolean mFlywheel;
+
+ private float mVelocity;
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ private static final float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
+ private static final float ALPHA = 800; // pixels / seconds
+ private static final float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
+ private static final float END_TENSION = 1.0f - START_TENSION;
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE = new float[NB_SAMPLES + 1];
+
+ private float mDeceleration;
+ private final float mPpi;
+
+ static {
+ float xMin = 0.0f;
+ for (int i = 0; i <= NB_SAMPLES; i++) {
+ final float t = (float) i / NB_SAMPLES;
+ float xMax = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = xMin + (xMax - xMin) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
+ if (Math.abs(tx - t) < 1E-5) break;
+ if (tx > t) xMax = x;
+ else xMin = x;
+ }
+ final float d = coef + x * x * x;
+ SPLINE[i] = d;
+ }
+ SPLINE[NB_SAMPLES] = 1.0f;
+
+ // This controls the viscous fluid effect (how much of it)
+ sViscousFluidScale = 8.0f;
+ // must be set to 1.0 (used in viscousFluid())
+ sViscousFluidNormalize = 1.0f;
+ sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+ }
+
+ private static float sViscousFluidScale;
+ private static float sViscousFluidNormalize;
+
+ /**
+ * Create a Scroller with the default duration and interpolator.
+ */
+ public Scroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
+ * be in effect for apps targeting Honeycomb or newer.
+ */
+ public Scroller(Context context, Interpolator interpolator) {
+ this(context, interpolator,
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. Specify whether or
+ * not to support progressive "flywheel" behavior in flinging.
+ */
+ public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
+ mFinished = true;
+ mInterpolator = interpolator;
+ mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
+ mFlywheel = flywheel;
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link android.view.ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mDeceleration = computeDeceleration(friction);
+ }
+
+ private float computeDeceleration(float friction) {
+ return SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * mPpi // pixels per inch
+ * friction;
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mFinished = finished;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ */
+ public final int getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mCurrX;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mCurrY;
+ }
+
+ /**
+ * Returns the current velocity.
+ *
+ * @return The original velocity less the deceleration. Result may be
+ * negative.
+ */
+ public float getCurrVelocity() {
+ return mVelocity - mDeceleration * timePassed() / 2000.0f;
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mStartX;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mStartY;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mFinalX;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mFinalY;
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true,
+ * the animation is not yet finished. loc will be altered to provide the
+ * new location.
+ */
+ public boolean computeScrollOffset() {
+ if (mFinished) {
+ return false;
+ }
+
+ int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+
+ if (timePassed < mDuration) {
+ switch (mMode) {
+ case SCROLL_MODE:
+ float x = timePassed * mDurationReciprocal;
+
+ if (mInterpolator == null)
+ x = viscousFluid(x);
+ else
+ x = mInterpolator.getInterpolation(x);
+
+ mCurrX = mStartX + Math.round(x * mDeltaX);
+ mCurrY = mStartY + Math.round(x * mDeltaY);
+ break;
+ case FLING_MODE:
+ final float t = (float) timePassed / mDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ final float tInf = (float) index / NB_SAMPLES;
+ final float tSup = (float) (index + 1) / NB_SAMPLES;
+ final float dInf = SPLINE[index];
+ final float dSup = SPLINE[index + 1];
+ final float distanceCoef = dInf + (t - tInf) / (tSup - tInf) * (dSup - dInf);
+
+ mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
+ // Pin to mMinX <= mCurrX <= mMaxX
+ mCurrX = Math.min(mCurrX, mMaxX);
+ mCurrX = Math.max(mCurrX, mMinX);
+
+ mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
+ // Pin to mMinY <= mCurrY <= mMaxY
+ mCurrY = Math.min(mCurrY, mMaxY);
+ mCurrY = Math.max(mCurrY, mMinY);
+
+ if (mCurrX == mFinalX && mCurrY == mFinalY) {
+ mFinished = true;
+ }
+
+ break;
+ }
+ } else {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mFinished = false;
+ mDuration = duration;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+ mFinalX = startX + dx;
+ mFinalY = startY + dy;
+ mDeltaX = dx;
+ mDeltaY = dy;
+ mDurationReciprocal = 1.0f / (float) mDuration;
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance travelled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this
+ * point.
+ * @param maxX Maximum X value. The scroller will not scroll past this
+ * point.
+ * @param minY Minimum Y value. The scroller will not scroll past this
+ * point.
+ * @param maxY Maximum Y value. The scroller will not scroll past this
+ * point.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !mFinished) {
+ float oldVel = getCurrVelocity();
+
+ float dx = (float) (mFinalX - mStartX);
+ float dy = (float) (mFinalY - mStartY);
+ float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+
+ float ndx = dx / hyp;
+ float ndy = dy / hyp;
+
+ float oldVelocityX = ndx * oldVel;
+ float oldVelocityY = ndy * oldVel;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX)
+ && Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mFinished = false;
+
+ float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
+
+ mVelocity = velocity;
+ final double l = Math.log(START_TENSION * velocity / ALPHA);
+ mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+
+ float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
+ float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
+
+ int totalDistance =
+ (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+
+ mMinX = minX;
+ mMaxX = maxX;
+ mMinY = minY;
+ mMaxY = maxY;
+
+ mFinalX = startX + Math.round(totalDistance * coeffX);
+ // Pin to mMinX <= mFinalX <= mMaxX
+ mFinalX = Math.min(mFinalX, mMaxX);
+ mFinalX = Math.max(mFinalX, mMinX);
+
+ mFinalY = startY + Math.round(totalDistance * coeffY);
+ // Pin to mMinY <= mFinalY <= mMaxY
+ mFinalY = Math.min(mFinalY, mMaxY);
+ mFinalY = Math.max(mFinalY, mMinY);
+ }
+
+ static float viscousFluid(float x) {
+ x *= sViscousFluidScale;
+ if (x < 1.0f) {
+ x -= (1.0f - (float) Math.exp(-x));
+ } else {
+ float start = 0.36787944117f; // 1/e == exp(-1)
+ x = 1.0f - (float) Math.exp(1.0f - x);
+ x = start + x * (1.0f - start);
+ }
+ x *= sViscousFluidNormalize;
+ return x;
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating cause the scroller to move to the final x and y
+ * position
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ */
+ public void extendDuration(int extend) {
+ int passed = timePassed();
+ mDuration = passed + extend;
+ mDurationReciprocal = 1.0f / mDuration;
+ mFinished = false;
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ */
+ public int timePassed() {
+ return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ */
+ public void setFinalX(int newX) {
+ mFinalX = newX;
+ mDeltaX = mFinalX - mStartX;
+ mFinished = false;
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ */
+ public void setFinalY(int newY) {
+ mFinalY = newY;
+ mDeltaY = mFinalY - mStartY;
+ mFinished = false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX)
+ && Math.signum(yvel) == Math.signum(mFinalY - mStartY);
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/SinusoidalInterpolator.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/SinusoidalInterpolator.java
new file mode 100644
index 0000000000..e79051a06b
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/SinusoidalInterpolator.java
@@ -0,0 +1,15 @@
+package net.simonvt.menudrawer;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Interpolator which, when drawn from 0 to 1, looks like half a sine-wave. Used for smoother opening/closing when
+ * peeking at the drawer.
+ */
+public class SinusoidalInterpolator implements Interpolator {
+
+ @Override
+ public float getInterpolation(float input) {
+ return (float) (0.5f + 0.5f * Math.sin(input * Math.PI - Math.PI / 2.f));
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/SmoothInterpolator.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/SmoothInterpolator.java
new file mode 100644
index 0000000000..cfdcb69bf3
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/SmoothInterpolator.java
@@ -0,0 +1,12 @@
+package net.simonvt.menudrawer;
+
+import android.view.animation.Interpolator;
+
+public class SmoothInterpolator implements Interpolator {
+
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/StaticDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/StaticDrawer.java
new file mode 100644
index 0000000000..879f2c9504
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/StaticDrawer.java
@@ -0,0 +1,208 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+
+public abstract class StaticDrawer extends MenuDrawer {
+
+ protected Position mPosition;
+
+ StaticDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public StaticDrawer(Context context) {
+ super(context);
+ }
+
+ public StaticDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public StaticDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (mDropShadowEnabled) drawDropShadow(canvas);
+ if (mActiveIndicator != null) drawIndicator(canvas);
+ }
+
+ private void drawDropShadow(Canvas canvas) {
+ final int width = getWidth();
+ final int height = getHeight();
+ final int menuSize = mMenuSize;
+ final int dropShadowSize = mDropShadowSize;
+
+ switch (mPosition) {
+ case LEFT:
+ mDropShadowDrawable.setBounds(menuSize - dropShadowSize, 0, menuSize, height);
+ break;
+
+ case TOP:
+ mDropShadowDrawable.setBounds(0, menuSize - dropShadowSize, width, menuSize);
+ break;
+
+ case RIGHT:
+ mDropShadowDrawable.setBounds(width - menuSize, 0, width - menuSize + dropShadowSize, height);
+ break;
+
+ case BOTTOM:
+ mDropShadowDrawable.setBounds(0, height - menuSize, width, height - menuSize + dropShadowSize);
+ break;
+ }
+
+ mDropShadowDrawable.draw(canvas);
+ }
+
+ protected abstract void drawIndicator(Canvas canvas);
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int width = r - l;
+ final int height = b - t;
+
+ switch (mPosition) {
+ case LEFT:
+ mMenuContainer.layout(0, 0, mMenuSize, height);
+ mContentContainer.layout(mMenuSize, 0, width, height);
+ break;
+
+ case RIGHT:
+ mMenuContainer.layout(width - mMenuSize, 0, width, height);
+ mContentContainer.layout(0, 0, width - mMenuSize, height);
+ break;
+
+ case TOP:
+ mMenuContainer.layout(0, 0, width, mMenuSize);
+ mContentContainer.layout(0, mMenuSize, width, height);
+ break;
+
+ case BOTTOM:
+ mMenuContainer.layout(0, height - mMenuSize, width, height);
+ mContentContainer.layout(0, 0, width, height - mMenuSize);
+ break;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Must measure with an exact size");
+ }
+
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (!mMenuSizeSet) mMenuSize = (int) (height * 0.25f);
+
+ switch (mPosition) {
+ case LEFT:
+ case RIGHT: {
+ final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+ final int menuWidth = mMenuSize;
+ final int menuWidthMeasureSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY);
+
+ final int contentWidth = width - menuWidth;
+ final int contentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
+
+ mContentContainer.measure(contentWidthMeasureSpec, childHeightMeasureSpec);
+ mMenuContainer.measure(menuWidthMeasureSpec, childHeightMeasureSpec);
+ break;
+ }
+
+ case TOP:
+ case BOTTOM: {
+ final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+
+ final int menuHeight = mMenuSize;
+ final int menuHeightMeasureSpec = MeasureSpec.makeMeasureSpec(menuHeight, MeasureSpec.EXACTLY);
+
+ final int contentHeight = height - menuHeight;
+ final int contentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
+
+ mContentContainer.measure(childWidthMeasureSpec, contentHeightMeasureSpec);
+ mMenuContainer.measure(childWidthMeasureSpec, menuHeightMeasureSpec);
+ break;
+ }
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ public void toggleMenu(boolean animate) {
+ }
+
+ @Override
+ public void openMenu(boolean animate) {
+ }
+
+ @Override
+ public void closeMenu(boolean animate) {
+ }
+
+ @Override
+ public boolean isMenuVisible() {
+ return true;
+ }
+
+ @Override
+ public void setMenuSize(int size) {
+ mMenuSize = size;
+ mMenuSizeSet = true;
+ requestLayout();
+ invalidate();
+ }
+
+ @Override
+ public void setOffsetMenuEnabled(boolean offsetMenu) {
+ }
+
+ @Override
+ public boolean getOffsetMenuEnabled() {
+ return false;
+ }
+
+ @Override
+ public void peekDrawer() {
+ }
+
+ @Override
+ public void peekDrawer(long delay) {
+ }
+
+ @Override
+ public void peekDrawer(long startDelay, long delay) {
+ }
+
+ @Override
+ public void setHardwareLayerEnabled(boolean enabled) {
+ }
+
+ @Override
+ public int getTouchMode() {
+ return TOUCH_MODE_NONE;
+ }
+
+ @Override
+ public void setTouchMode(int mode) {
+ }
+
+ @Override
+ public void setTouchBezelSize(int size) {
+ }
+
+ @Override
+ public int getTouchBezelSize() {
+ return 0;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/TopDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/TopDrawer.java
new file mode 100644
index 0000000000..0af2c6bad8
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/TopDrawer.java
@@ -0,0 +1,216 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+public class TopDrawer extends VerticalDrawer {
+
+ private int mIndicatorLeft;
+
+ TopDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public TopDrawer(Context context) {
+ super(context);
+ }
+
+ public TopDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TopDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void openMenu(boolean animate) {
+ animateOffsetTo(mMenuSize, 0, animate);
+ }
+
+ @Override
+ public void closeMenu(boolean animate) {
+ animateOffsetTo(0, 0, animate);
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP,
+ new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int width = r - l;
+ final int height = b - t;
+ final int offsetPixels = (int) mOffsetPixels;
+
+ mMenuContainer.layout(0, 0, width, mMenuSize);
+ offsetMenu(offsetPixels);
+
+ if (USE_TRANSLATIONS) {
+ mContentContainer.layout(0, 0, width, height);
+ } else {
+ mContentContainer.layout(0, offsetPixels, width, height + offsetPixels);
+ }
+ }
+
+ /**
+ * Offsets the menu relative to its original position based on the position of the content.
+ *
+ * @param offsetPixels The number of pixels the content if offset.
+ */
+ private void offsetMenu(int offsetPixels) {
+ if (mOffsetMenu && mMenuSize != 0) {
+ final int menuSize = mMenuSize;
+ final float openRatio = (menuSize - (float) offsetPixels) / menuSize;
+
+ if (USE_TRANSLATIONS) {
+ if (offsetPixels > 0) {
+ final int offset = (int) (0.25f * (-openRatio * menuSize));
+ mMenuContainer.setTranslationY(offset);
+ } else {
+ mMenuContainer.setTranslationY(-menuSize);
+ }
+
+ } else {
+ final int oldMenuTop = mMenuContainer.getTop();
+ final int offset = (int) (0.25f * (-openRatio * menuSize)) - oldMenuTop;
+ mMenuContainer.offsetTopAndBottom(offset);
+ mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ protected void drawDropShadow(Canvas canvas, int offsetPixels) {
+ final int width = getWidth();
+
+ mDropShadowDrawable.setBounds(0, offsetPixels - mDropShadowSize, width, offsetPixels);
+ mDropShadowDrawable.draw(canvas);
+ }
+
+ @Override
+ protected void drawMenuOverlay(Canvas canvas, int offsetPixels) {
+ final int width = getWidth();
+ final float openRatio = ((float) offsetPixels) / mMenuSize;
+
+ mMenuOverlay.setBounds(0, 0, width, offsetPixels);
+ mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio)));
+ mMenuOverlay.draw(canvas);
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas, int offsetPixels) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ final int menuHeight = mMenuSize;
+ final int indicatorHeight = mActiveIndicator.getHeight();
+
+ final float openRatio = ((float) offsetPixels) / menuHeight;
+
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+ final int indicatorWidth = mActiveIndicator.getWidth();
+
+ final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio));
+ final int interpolatedHeight = (int) (indicatorHeight * interpolatedRatio);
+
+ final int indicatorTop = offsetPixels - interpolatedHeight;
+ if (mIndicatorAnimating) {
+ final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ final int startLeft = mIndicatorStartPos;
+ final int diff = finalLeft - startLeft;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorLeft = startLeft + startOffset;
+ } else {
+ mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ }
+
+ canvas.save();
+ canvas.clipRect(mIndicatorLeft, indicatorTop, mIndicatorLeft + indicatorWidth, offsetPixels);
+ canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorLeft;
+ }
+
+ @Override
+ protected void initPeekScroller() {
+ final int dx = mMenuSize / 3;
+ mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
+ }
+
+ @Override
+ protected void onOffsetPixelsChanged(int offsetPixels) {
+ if (USE_TRANSLATIONS) {
+ mContentContainer.setTranslationY(offsetPixels);
+ offsetMenu(offsetPixels);
+ invalidate();
+ } else {
+ mContentContainer.offsetTopAndBottom(offsetPixels - mContentContainer.getTop());
+ offsetMenu(offsetPixels);
+ invalidate();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // Touch handling
+ //////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected boolean isContentTouch(MotionEvent ev) {
+ return ev.getY() > mOffsetPixels;
+ }
+
+ @Override
+ protected boolean onDownAllowDrag(MotionEvent ev) {
+ return (!mMenuVisible && mInitialMotionY <= mTouchSize)
+ || (mMenuVisible && mInitialMotionY >= mOffsetPixels);
+ }
+
+ @Override
+ protected boolean onMoveAllowDrag(MotionEvent ev, float diff) {
+ return (!mMenuVisible && mInitialMotionY <= mTouchSize && (diff > 0))
+ || (mMenuVisible && mInitialMotionY >= mOffsetPixels);
+ }
+
+ @Override
+ protected void onMoveEvent(float dx) {
+ setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
+ }
+
+ @Override
+ protected void onUpEvent(MotionEvent ev) {
+ final int offsetPixels = (int) mOffsetPixels;
+
+ if (mIsDragging) {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ final int initialVelocity = (int) mVelocityTracker.getXVelocity();
+ mLastMotionY = ev.getY();
+ animateOffsetTo(mVelocityTracker.getYVelocity() > 0 ? mMenuSize : 0, initialVelocity,
+ true);
+
+ // Close the menu when content is clicked while the menu is visible.
+ } else if (mMenuVisible && ev.getY() > offsetPixels) {
+ closeMenu();
+ }
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/TopStaticDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/TopStaticDrawer.java
new file mode 100644
index 0000000000..ac846148b1
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/TopStaticDrawer.java
@@ -0,0 +1,82 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+
+public class TopStaticDrawer extends StaticDrawer {
+
+ private int mIndicatorLeft;
+
+ TopStaticDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public TopStaticDrawer(Context context) {
+ super(context);
+ }
+
+ public TopStaticDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TopStaticDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super.initDrawer(context, attrs, defStyle);
+ mPosition = Position.TOP;
+ }
+
+ @Override
+ public void setDropShadowColor(int color) {
+ final int endColor = color & 0x00FFFFFF;
+ mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, new int[] {
+ color,
+ endColor,
+ });
+ invalidate();
+ }
+
+ @Override
+ protected void drawIndicator(Canvas canvas) {
+ if (mActiveView != null && isViewDescendant(mActiveView)) {
+ Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
+ final int pos = position == null ? 0 : position;
+
+ if (pos == mActivePosition) {
+ final int menuHeight = mMenuSize;
+ final int indicatorHeight = mActiveIndicator.getHeight();
+
+ mActiveView.getDrawingRect(mActiveRect);
+ offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
+ final int indicatorWidth = mActiveIndicator.getWidth();
+
+ final int indicatorTop = menuHeight - indicatorHeight;
+ if (mIndicatorAnimating) {
+ final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ final int startLeft = mIndicatorStartPos;
+ final int diff = finalLeft - startLeft;
+ final int startOffset = (int) (diff * mIndicatorOffset);
+ mIndicatorLeft = startLeft + startOffset;
+ } else {
+ mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
+ }
+
+ canvas.save();
+ canvas.clipRect(mIndicatorLeft, indicatorTop, mIndicatorLeft + indicatorWidth, menuHeight);
+ canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected int getIndicatorStartPos() {
+ return mIndicatorLeft;
+ }
+}
diff --git a/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/VerticalDrawer.java b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/VerticalDrawer.java
new file mode 100644
index 0000000000..415d49896e
--- /dev/null
+++ b/Externals/android-menudrawer/library/src/net/simonvt/menudrawer/VerticalDrawer.java
@@ -0,0 +1,216 @@
+package net.simonvt.menudrawer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+public abstract class VerticalDrawer extends DraggableDrawer {
+
+ VerticalDrawer(Activity activity, int dragMode) {
+ super(activity, dragMode);
+ }
+
+ public VerticalDrawer(Context context) {
+ super(context);
+ }
+
+ public VerticalDrawer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public VerticalDrawer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Must measure with an exact size");
+ }
+
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (!mMenuSizeSet) mMenuSize = (int) (height * 0.25f);
+ if (mOffsetPixels == -1) openMenu(false);
+
+ final int menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
+ final int menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
+ mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
+
+ final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
+ final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
+ mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
+
+ setMeasuredDimension(width, height);
+
+ updateTouchAreaSize();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
+ setOffsetPixels(0);
+ stopAnimation();
+ endPeek();
+ setDrawerState(STATE_CLOSED);
+ }
+
+ // Always intercept events over the content while menu is visible.
+ if (mMenuVisible && isContentTouch(ev)) {
+ return true;
+ }
+
+ if (mTouchMode == TOUCH_MODE_NONE) {
+ return false;
+ }
+
+ if (action != MotionEvent.ACTION_DOWN) {
+ if (mIsDragging) {
+ return true;
+ }
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ final boolean allowDrag = onDownAllowDrag(ev);
+
+ if (allowDrag) {
+ setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
+ stopAnimation();
+ endPeek();
+ mIsDragging = false;
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ final float x = ev.getX();
+ final float dx = x - mLastMotionX;
+ final float xDiff = Math.abs(dx);
+ final float y = ev.getY();
+ final float dy = y - mLastMotionY;
+ final float yDiff = Math.abs(dy);
+
+ if (yDiff > mTouchSlop && yDiff > xDiff) {
+ if (mOnInterceptMoveEventListener != null && mTouchMode == TOUCH_MODE_FULLSCREEN
+ && canChildScrollVertically(mContentContainer, false, (int) dx, (int) x, (int) y)) {
+ endDrag(); // Release the velocity tracker
+ return false;
+ }
+
+ final boolean allowDrag = onMoveAllowDrag(ev, dy);
+
+ if (allowDrag) {
+ setDrawerState(STATE_DRAGGING);
+ mIsDragging = true;
+ mLastMotionX = x;
+ mLastMotionY = y;
+ }
+ }
+ break;
+ }
+
+ /**
+ * If you click really fast, an up or cancel event is delivered here. Just snap content to
+ * whatever is closest.
+ */
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
+ openMenu();
+ } else {
+ closeMenu();
+ }
+ break;
+ }
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ return mIsDragging;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mMenuVisible && !mIsDragging && (mTouchMode == TOUCH_MODE_NONE)) {
+ return false;
+ }
+ final int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ final boolean allowDrag = onDownAllowDrag(ev);
+
+ if (allowDrag) {
+ stopAnimation();
+ endPeek();
+ startLayerTranslation();
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ if (!mIsDragging) {
+ final float x = ev.getX();
+ final float dx = x - mLastMotionX;
+ final float xDiff = Math.abs(dx);
+ final float y = ev.getY();
+ final float dy = y - mLastMotionY;
+ final float yDiff = Math.abs(dy);
+
+ if (yDiff > mTouchSlop && yDiff > xDiff) {
+ final boolean allowDrag = onMoveAllowDrag(ev, dy);
+
+ if (allowDrag) {
+ setDrawerState(STATE_DRAGGING);
+ mIsDragging = true;
+ mLastMotionY = y - mInitialMotionY > 0
+ ? mInitialMotionY + mTouchSlop
+ : mInitialMotionY - mTouchSlop;
+ }
+ }
+ }
+
+ if (mIsDragging) {
+ startLayerTranslation();
+
+ final float y = ev.getY();
+ final float dy = y - mLastMotionY;
+
+ mLastMotionY = y;
+ onMoveEvent(dy);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ onUpEvent(ev);
+ break;
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/Externals/android-menudrawer/pom.xml b/Externals/android-menudrawer/pom.xml
new file mode 100644
index 0000000000..eda6dd9125
--- /dev/null
+++ b/Externals/android-menudrawer/pom.xml
@@ -0,0 +1,126 @@
+
+
+
+ 4.0.0
+
+
+ org.sonatype.oss
+ oss-parent
+ 7
+
+
+ net.simonvt
+ android-menudrawer-parent
+ pom
+ 2.0.3-SNAPSHOT
+
+ Android MenuDrawer (Parent)
+ A menu drawer implementation which allows dragging of both the content, and the entire window.
+ https://github.com/SimonVT/android-menudrawer
+ 2012
+
+
+ library
+ samples
+
+
+
+ http://github.com/SimonVT/android-menudrawer/
+ scm:git:git://github.com/SimonVT/android-menudrawer.git
+ scm:git:git@github.com:SimonVT/android-menudrawer.git
+ HEAD
+
+
+
+
+ Apache License Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ GitHub Issues
+ https://github.com/SimonVT/android-menudrawer/issues
+
+
+
+ UTF-8
+ UTF-8
+
+ 1.6
+ 4.1.1.4
+ 16
+
+ r7
+
+
+
+
+
+ com.google.android
+ android
+ ${android.version}
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.0
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+ 3.5.1
+
+
+ ${android.platform}
+
+
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ 2.4
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 2.9.1
+
+ true
+
+ ../checkstyle.xml
+ true
+
+
+
+ verify
+
+ checkstyle
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/AndroidManifest.xml b/Externals/android-menudrawer/samples/AndroidManifest.xml
new file mode 100644
index 0000000000..7323fa61b3
--- /dev/null
+++ b/Externals/android-menudrawer/samples/AndroidManifest.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/build.xml b/Externals/android-menudrawer/samples/build.xml
new file mode 100644
index 0000000000..9eb8fb98ea
--- /dev/null
+++ b/Externals/android-menudrawer/samples/build.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/libs/android-support-v4-r10.jar b/Externals/android-menudrawer/samples/libs/android-support-v4-r10.jar
new file mode 100644
index 0000000000..feaf44f801
Binary files /dev/null and b/Externals/android-menudrawer/samples/libs/android-support-v4-r10.jar differ
diff --git a/Externals/android-menudrawer/samples/pom.xml b/Externals/android-menudrawer/samples/pom.xml
new file mode 100644
index 0000000000..5baaac769b
--- /dev/null
+++ b/Externals/android-menudrawer/samples/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+ 4.0.0
+
+
+ net.simonvt
+ android-menudrawer-parent
+ 2.0.3-SNAPSHOT
+ ../pom.xml
+
+
+ android-menudrawer-sample
+ Android MenuDrawer Sample
+ apk
+
+
+
+ com.google.android
+ android
+ provided
+
+
+ net.simonvt
+ android-menudrawer
+ ${project.version}
+ apklib
+
+
+ com.google.android
+ support-v4
+ ${android-support.version}
+
+
+
+
+ src
+
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+ true
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/project.properties b/Externals/android-menudrawer/samples/project.properties
new file mode 100644
index 0000000000..1561d7a9a1
--- /dev/null
+++ b/Externals/android-menudrawer/samples/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
+android.library.reference.1=../library
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow.png b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow.png
new file mode 100644
index 0000000000..add3b36186
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_bottom.png b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_bottom.png
new file mode 100644
index 0000000000..47e9c9045c
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_bottom.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_right.png b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_right.png
new file mode 100644
index 0000000000..4ae9df9b78
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_right.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_top.png b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_top.png
new file mode 100644
index 0000000000..ab4ed80eea
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi-v14/menu_arrow_top.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_action_refresh_dark.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_action_refresh_dark.png
new file mode 100644
index 0000000000..bb9d855f77
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_action_refresh_dark.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_action_select_all_dark.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_action_select_all_dark.png
new file mode 100644
index 0000000000..c87c3f8370
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_action_select_all_dark.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_launcher.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..96a442e5b8
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_launcher.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png
new file mode 100644
index 0000000000..bb6aef1d06
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/md__category_background.9.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__category_background.9.png
new file mode 100644
index 0000000000..43a20adcb1
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__category_background.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_focused_holo.9.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_focused_holo.9.png
new file mode 100644
index 0000000000..555270842a
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_focused_holo.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_longpressed_holo.9.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_longpressed_holo.9.png
new file mode 100644
index 0000000000..4ea7afa00e
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_longpressed_holo.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_pressed_holo_dark.9.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_pressed_holo_dark.9.png
new file mode 100644
index 0000000000..5654cd6942
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_pressed_holo_dark.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png
new file mode 100644
index 0000000000..f6fd30dcdc
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow.png
new file mode 100644
index 0000000000..c28d98f3bb
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_bottom.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_bottom.png
new file mode 100644
index 0000000000..4cbe8cbed4
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_bottom.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_right.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_right.png
new file mode 100644
index 0000000000..2fb6a937e6
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_right.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_top.png b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_top.png
new file mode 100644
index 0000000000..41f6c6a859
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-hdpi/menu_arrow_top.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow.png b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow.png
new file mode 100644
index 0000000000..26d7759516
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_bottom.png b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_bottom.png
new file mode 100644
index 0000000000..af8e7b909d
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_bottom.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_right.png b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_right.png
new file mode 100644
index 0000000000..21deedfcac
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_right.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_top.png b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_top.png
new file mode 100644
index 0000000000..1f5d61a278
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi-v14/menu_arrow_top.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_action_refresh_dark.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_action_refresh_dark.png
new file mode 100644
index 0000000000..bd611e8e24
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_action_refresh_dark.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_action_select_all_dark.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_action_select_all_dark.png
new file mode 100644
index 0000000000..252ecbb0ed
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_action_select_all_dark.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_launcher.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..359047dfa4
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_launcher.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png
new file mode 100644
index 0000000000..01d681697f
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/md__category_background.9.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__category_background.9.png
new file mode 100644
index 0000000000..af0bc168d5
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__category_background.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_focused_holo.9.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_focused_holo.9.png
new file mode 100644
index 0000000000..00f05d8c97
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_focused_holo.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_longpressed_holo.9.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_longpressed_holo.9.png
new file mode 100644
index 0000000000..3bf8e03623
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_longpressed_holo.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_pressed_holo_dark.9.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_pressed_holo_dark.9.png
new file mode 100644
index 0000000000..6e77525d2d
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_pressed_holo_dark.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png
new file mode 100644
index 0000000000..92da2f0dd3
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow.png
new file mode 100644
index 0000000000..ecacda7113
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_bottom.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_bottom.png
new file mode 100644
index 0000000000..3dce461a3d
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_bottom.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_right.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_right.png
new file mode 100644
index 0000000000..3222d034e9
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_right.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_top.png b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_top.png
new file mode 100644
index 0000000000..cde43fd2b1
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-mdpi/menu_arrow_top.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow.png
new file mode 100644
index 0000000000..6bdcc62e1a
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_bottom.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_bottom.png
new file mode 100644
index 0000000000..aba9001f33
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_bottom.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_right.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_right.png
new file mode 100644
index 0000000000..85591b81d5
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_right.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_top.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_top.png
new file mode 100644
index 0000000000..b81c74749d
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi-v14/menu_arrow_top.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_action_refresh_dark.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_action_refresh_dark.png
new file mode 100644
index 0000000000..a7fdc0dfcb
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_action_refresh_dark.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_action_select_all_dark.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_action_select_all_dark.png
new file mode 100644
index 0000000000..f193a8bb31
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_action_select_all_dark.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_launcher.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..71c6d760f0
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.png
new file mode 100644
index 0000000000..930ca8d95e
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__category_background.9.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__category_background.9.png
new file mode 100644
index 0000000000..942d72e65b
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__category_background.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_focused_holo.9.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_focused_holo.9.png
new file mode 100644
index 0000000000..b545f8e578
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_focused_holo.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_longpressed_holo.9.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_longpressed_holo.9.png
new file mode 100644
index 0000000000..eda10e6123
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_longpressed_holo.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_pressed_holo_dark.9.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_pressed_holo_dark.9.png
new file mode 100644
index 0000000000..e4b33935a3
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_pressed_holo_dark.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png
new file mode 100644
index 0000000000..88726b6916
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow.png
new file mode 100644
index 0000000000..b159937237
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_bottom.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_bottom.png
new file mode 100644
index 0000000000..77a088fdce
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_bottom.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_right.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_right.png
new file mode 100644
index 0000000000..17b5250d1d
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_right.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_top.png b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_top.png
new file mode 100644
index 0000000000..2c2f749008
Binary files /dev/null and b/Externals/android-menudrawer/samples/res/drawable-xhdpi/menu_arrow_top.png differ
diff --git a/Externals/android-menudrawer/samples/res/drawable/md__list_selector_background_transition_holo_dark.xml b/Externals/android-menudrawer/samples/res/drawable/md__list_selector_background_transition_holo_dark.xml
new file mode 100644
index 0000000000..1d43396d8a
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/drawable/md__list_selector_background_transition_holo_dark.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/drawable/md__list_selector_holo_dark.xml b/Externals/android-menudrawer/samples/res/drawable/md__list_selector_holo_dark.xml
new file mode 100644
index 0000000000..bd71aa8b6e
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/drawable/md__list_selector_holo_dark.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_bottommenu.xml b/Externals/android-menudrawer/samples/res/layout/activity_bottommenu.xml
new file mode 100644
index 0000000000..ee576195e2
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_bottommenu.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_contentsample.xml b/Externals/android-menudrawer/samples/res/layout/activity_contentsample.xml
new file mode 100644
index 0000000000..54140e71e7
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_contentsample.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_layoutsample.xml b/Externals/android-menudrawer/samples/res/layout/activity_layoutsample.xml
new file mode 100644
index 0000000000..83684116b4
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_layoutsample.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_rightmenu.xml b/Externals/android-menudrawer/samples/res/layout/activity_rightmenu.xml
new file mode 100644
index 0000000000..ad1a6b7601
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_rightmenu.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_staticsample.xml b/Externals/android-menudrawer/samples/res/layout/activity_staticsample.xml
new file mode 100644
index 0000000000..e6cb5622c7
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_staticsample.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_topmenu.xml b/Externals/android-menudrawer/samples/res/layout/activity_topmenu.xml
new file mode 100644
index 0000000000..d288cfa5aa
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_topmenu.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_viewpagersample.xml b/Externals/android-menudrawer/samples/res/layout/activity_viewpagersample.xml
new file mode 100644
index 0000000000..6efa747ebe
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_viewpagersample.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/Externals/android-menudrawer/samples/res/layout/activity_windowsample.xml b/Externals/android-menudrawer/samples/res/layout/activity_windowsample.xml
new file mode 100644
index 0000000000..a23003f22b
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/activity_windowsample.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/list_row_sample.xml b/Externals/android-menudrawer/samples/res/layout/list_row_sample.xml
new file mode 100644
index 0000000000..edd6d1f67a
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/list_row_sample.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/main.xml b/Externals/android-menudrawer/samples/res/layout/main.xml
new file mode 100644
index 0000000000..6dd71bd26a
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/main.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/menu_bottom.xml b/Externals/android-menudrawer/samples/res/layout/menu_bottom.xml
new file mode 100644
index 0000000000..4ec6a6b993
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/menu_bottom.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Externals/android-menudrawer/samples/res/layout/menu_row_category.xml b/Externals/android-menudrawer/samples/res/layout/menu_row_category.xml
new file mode 100644
index 0000000000..36ffd58bac
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/menu_row_category.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/menu_row_item.xml b/Externals/android-menudrawer/samples/res/layout/menu_row_item.xml
new file mode 100644
index 0000000000..b13619d08c
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/menu_row_item.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/menu_scrollview.xml b/Externals/android-menudrawer/samples/res/layout/menu_scrollview.xml
new file mode 100644
index 0000000000..5cb1ad9350
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/menu_scrollview.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/layout/menu_top.xml b/Externals/android-menudrawer/samples/res/layout/menu_top.xml
new file mode 100644
index 0000000000..4ec6a6b993
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/layout/menu_top.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Externals/android-menudrawer/samples/res/values-v14/colors.xml b/Externals/android-menudrawer/samples/res/values-v14/colors.xml
new file mode 100644
index 0000000000..0c1ed99453
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/values-v14/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #FFE8E8E8
+
diff --git a/Externals/android-menudrawer/samples/res/values-v14/themes.xml b/Externals/android-menudrawer/samples/res/values-v14/themes.xml
new file mode 100644
index 0000000000..bc74b6d378
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/values-v14/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/values/colors.xml b/Externals/android-menudrawer/samples/res/values/colors.xml
new file mode 100644
index 0000000000..b98e21355e
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #00000000
+
+ #FF555555
+
diff --git a/Externals/android-menudrawer/samples/res/values/strings.xml b/Externals/android-menudrawer/samples/res/values/strings.xml
new file mode 100644
index 0000000000..90f33a30a9
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/values/strings.xml
@@ -0,0 +1,34 @@
+
+
+
+ MenuDrawerSamples
+
+ Sample of dragging the content.\nThe menu consists of a ListView, for which the
+ active position is saved through configuration changes.\nAdditionally, if the menu is open, a back press will
+ close it.
+
+
+ Sample of dragging the entire window.\nThe menu is a simple ScrollView, with a click
+ listener set on each item.\nThe drawer will animate slightly open, to make it more discoverable.\nAdditionally,
+ if the menu is open, a back press will close it.
+
+
+ Sample of how to use the drawer with ListActivity.\nWhen running on ICS or
+ greater, the ActionBar up button will blink until clicked by the user. This is done to make the menu more
+ discoverable.\nAdditionally, if the menu is open, a back press will close it.
+
+
+ This sample shows how to position the menu to the right of the content.\nRun on
+ an API14+ device to see how it interacts with the overflow button.
+
+
+ This sample shows how to position the menu above the content.
+
+ This sample shows how to position the menu below the content.
+
+ This sample shows how to use the drawer in a layout file.
+ \nThe top drawer is used in this sample.
+
+
+ This sample shows how to attach a drawer that is always visible.
+
diff --git a/Externals/android-menudrawer/samples/res/values/styles.xml b/Externals/android-menudrawer/samples/res/values/styles.xml
new file mode 100644
index 0000000000..04b6f5f355
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/values/styles.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/res/values/themes.xml b/Externals/android-menudrawer/samples/res/values/themes.xml
new file mode 100644
index 0000000000..653c78f39b
--- /dev/null
+++ b/Externals/android-menudrawer/samples/res/values/themes.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/ActionBarOverlaySample.java b/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/ActionBarOverlaySample.java
new file mode 100644
index 0000000000..e703a9506e
--- /dev/null
+++ b/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/ActionBarOverlaySample.java
@@ -0,0 +1,13 @@
+package net.simonvt.menudrawer.samples;
+
+import android.os.Bundle;
+import android.view.Window;
+
+public class ActionBarOverlaySample extends WindowSample {
+
+ @Override
+ public void onCreate(Bundle inState) {
+ requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ super.onCreate(inState);
+ }
+}
diff --git a/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/BottomMenuSample.java b/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/BottomMenuSample.java
new file mode 100644
index 0000000000..66cd73d671
--- /dev/null
+++ b/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/BottomMenuSample.java
@@ -0,0 +1,43 @@
+package net.simonvt.menudrawer.samples;
+
+import net.simonvt.menudrawer.MenuDrawer;
+import net.simonvt.menudrawer.Position;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+/**
+ * Sample class illustrating how to add a menu drawer above the content area.
+ */
+public class BottomMenuSample extends Activity implements OnClickListener {
+
+ private MenuDrawer mMenuDrawer;
+ private TextView mContentTextView;
+
+ @Override
+ protected void onCreate(Bundle inState) {
+ super.onCreate(inState);
+
+ mMenuDrawer = MenuDrawer.attach(this, MenuDrawer.MENU_DRAG_CONTENT, Position.BOTTOM);
+ mMenuDrawer.setTouchMode(MenuDrawer.TOUCH_MODE_FULLSCREEN);
+ mMenuDrawer.setContentView(R.layout.activity_bottommenu);
+ mMenuDrawer.setMenuView(R.layout.menu_bottom);
+
+ mContentTextView = (TextView) findViewById(R.id.contentText);
+ findViewById(R.id.item1).setOnClickListener(this);
+ findViewById(R.id.item2).setOnClickListener(this);
+ }
+
+ /**
+ * Click handler for bottom drawer items.
+ */
+ @Override
+ public void onClick(View v) {
+ String tag = (String) v.getTag();
+ mContentTextView.setText(String.format("%s clicked.", tag));
+ mMenuDrawer.setActiveView(v);
+ }
+}
diff --git a/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/ContentSample.java b/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/ContentSample.java
new file mode 100644
index 0000000000..b07019ce67
--- /dev/null
+++ b/Externals/android-menudrawer/samples/src/net/simonvt/menudrawer/samples/ContentSample.java
@@ -0,0 +1,218 @@
+package net.simonvt.menudrawer.samples;
+
+import net.simonvt.menudrawer.MenuDrawer;
+
+import android.app.Activity;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ContentSample extends Activity {
+
+ private static final String STATE_ACTIVE_POSITION = "net.simonvt.menudrawer.samples.ContentSample.activePosition";
+ private static final String STATE_CONTENT_TEXT = "net.simonvt.menudrawer.samples.ContentSample.contentText";
+
+ private MenuDrawer mMenuDrawer;
+
+ private MenuAdapter mAdapter;
+ private ListView mList;
+
+ private int mActivePosition = -1;
+ private String mContentText;
+ private TextView mContentTextView;
+
+ @Override
+ protected void onCreate(Bundle inState) {
+ super.onCreate(inState);
+
+ if (inState != null) {
+ mActivePosition = inState.getInt(STATE_ACTIVE_POSITION);
+ mContentText = inState.getString(STATE_CONTENT_TEXT);
+ }
+
+ mMenuDrawer = MenuDrawer.attach(this, MenuDrawer.MENU_DRAG_CONTENT);
+ mMenuDrawer.setContentView(R.layout.activity_contentsample);
+ mMenuDrawer.setTouchMode(MenuDrawer.TOUCH_MODE_FULLSCREEN);
+
+ List