blob: 268769d2fe6cd559a981ca1fb2ecc23c2a2b0ac3 [file] [log] [blame]
Daniel Sandler4e1cd232011-05-12 00:06:32 -04001/*);
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// TODO:
18// background stellar matter:
19// - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift
20
21package com.android.launcher2;
22
23import android.animation.AnimatorSet;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040024import android.animation.ObjectAnimator;
25import android.animation.TimeAnimator;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040026import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040029import android.graphics.Bitmap;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040030import android.graphics.Point;
31import android.graphics.Rect;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040032import android.os.Handler;
Michael Jurka92f3d462011-11-22 21:02:29 -080033import android.support.v13.dreams.BasicDream;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040034import android.util.AttributeSet;
35import android.util.DisplayMetrics;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040036import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewGroup;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040039import android.widget.FrameLayout;
40import android.widget.ImageView;
Daniel Sandler4e1cd232011-05-12 00:06:32 -040041
42import com.android.launcher.R;
43
Michael Jurka92f3d462011-11-22 21:02:29 -080044import java.util.HashMap;
45import java.util.Random;
46
Daniel Sandler4e1cd232011-05-12 00:06:32 -040047public class RocketLauncher extends BasicDream {
48 public static final boolean ROCKET_LAUNCHER = true;
49
50 public static class Board extends FrameLayout
51 {
52 public static final boolean FIXED_STARS = true;
53 public static final boolean FLYING_STARS = true;
54 public static final int NUM_ICONS = 20;
55
56 public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed
57 private boolean mManeuveringThrusters = false;
58 private float mSpeedScale = 1.0f;
59
60 public static final int LAUNCH_ZOOM_TIME = 400; // ms
61
62 HashMap<ComponentName, Bitmap> mIcons;
63 ComponentName[] mComponentNames;
64
65 static Random sRNG = new Random();
66
67 static float lerp(float a, float b, float f) {
68 return (b-a)*f + a;
69 }
70
71 static float randfrange(float a, float b) {
72 return lerp(a, b, sRNG.nextFloat());
73 }
74
75 static int randsign() {
76 return sRNG.nextBoolean() ? 1 : -1;
77 }
78
79 static <E> E pick(E[] array) {
80 if (array.length == 0) return null;
81 return array[sRNG.nextInt(array.length)];
82 }
83
84 public class FlyingIcon extends ImageView {
85 public static final float VMAX = 1000.0f;
86 public static final float VMIN = 100.0f;
87 public static final float ANGULAR_VMAX = 45f;
88 public static final float ANGULAR_VMIN = 0f;
89 public static final float SCALE_MIN = 0.5f;
90 public static final float SCALE_MAX = 4f;
91
92 public float v, vr;
93
94 public final float[] hsv = new float[3];
95
96 public float angle, anglex, angley;
97 public float fuse;
98 public float dist;
99 public float endscale;
100 public float boardCenterX, boardCenterY;
101
102 public ComponentName component;
103
104 public FlyingIcon(Context context, AttributeSet as) {
105 super(context, as);
106 setLayerType(View.LAYER_TYPE_HARDWARE, null);
107
108 setBackgroundResource(R.drawable.flying_icon_bg);
109 //android.util.Log.d("RocketLauncher", "ctor: " + this);
110 hsv[1] = 1f;
111 hsv[2] = 1f;
112 }
113
114 @Override
115 public boolean onTouchEvent(MotionEvent event) {
116 if (!mManeuveringThrusters || component == null) {
117 return false;
118 }
119 if (getAlpha() < 0.5f) {
120 setPressed(false);
121 return false;
122 }
123
124 switch (event.getAction()) {
125 case MotionEvent.ACTION_DOWN:
126 setPressed(true);
127 Board.this.resetWarpTimer();
128 break;
129 case MotionEvent.ACTION_MOVE:
130 final Rect hit = new Rect();
131 final Point offset = new Point();
132 getGlobalVisibleRect(hit, offset);
133 final int globx = (int) event.getX() + offset.x;
134 final int globy = (int) event.getY() + offset.y;
135 setPressed(hit.contains(globx, globy));
136 Board.this.resetWarpTimer();
137 break;
138 case MotionEvent.ACTION_UP:
139 if (isPressed()) {
140 setPressed(false);
141 postDelayed(new Runnable() {
142 public void run() {
143 try {
144 getContext().startActivity(new Intent(Intent.ACTION_MAIN)
145 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
146 .setComponent(component));
147 } catch (android.content.ActivityNotFoundException e) {
148 } catch (SecurityException e) {
149 }
150 }
151 }, LAUNCH_ZOOM_TIME);
152 endscale = 0;
153 AnimatorSet s = new AnimatorSet();
154 s.playTogether(
155 ObjectAnimator.ofFloat(this, "scaleX", 15f),
156 ObjectAnimator.ofFloat(this, "scaleY", 15f),
157 ObjectAnimator.ofFloat(this, "alpha", 0f)
158 );
159
160 // make sure things are still moving until the very last instant the
161 // activity is visible
162 s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25));
163 s.setInterpolator(new android.view.animation.AccelerateInterpolator(3));
164 s.start();
165 }
166 break;
167 }
168 return true;
169 }
170
171 public String toString() {
172 return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>",
173 "icon", getX(), getY(), v, angle, dist, fuse);
174 }
175
176 public void randomizeIcon() {
177 component = pick(mComponentNames);
178 setImageBitmap(mIcons.get(component));
179 }
180
181 public void randomize() {
182 v = randfrange(VMIN, VMAX);
183 angle = randfrange(0, 360f);
184 anglex = (float) Math.sin(angle / 180. * Math.PI);
185 angley = (float) Math.cos(angle / 180. * Math.PI);
186 vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign();
187 endscale = randfrange(SCALE_MIN, SCALE_MAX);
188
189 randomizeIcon();
190 }
191 public void reset() {
192 randomize();
193 boardCenterX = (Board.this.getWidth() - getWidth()) / 2;
194 boardCenterY = (Board.this.getHeight() - getHeight()) / 2;
195 setX(boardCenterX);
196 setY(boardCenterY);
197 fuse = (float) Math.max(boardCenterX, boardCenterY);
198 setRotation(180-angle);
199 setScaleX(0f);
200 setScaleY(0f);
201 dist = 0;
202 setAlpha(0f);
203 }
204 public void update(float dt) {
205 dist += v * dt;
206 setX(getX() + anglex * v * dt);
207 setY(getY() + angley * v * dt);
208 //setRotation(getRotation() + vr * dt);
209 if (endscale > 0) {
210 float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse));
211 setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
212 setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
213 final float q1 = fuse*0.15f;
214 final float q4 = fuse*0.75f;
215 if (dist < q1) {
216 setAlpha((float) Math.sqrt(dist/q1));
217 } else if (dist > q4) {
218 setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2)));
219 } else {
220 setAlpha(1f);
221 }
222 }
223 }
224 }
225
226 public class FlyingStar extends FlyingIcon {
227 public FlyingStar(Context context, AttributeSet as) {
228 super(context, as);
229 }
230 public void randomizeIcon() {
231 setImageResource(R.drawable.widget_resize_handle_bottom);
232 }
233 public void randomize() {
234 super.randomize();
235 v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate
236 endscale = randfrange(1f, 2f); // ensmallen
237 }
238 }
239
240 TimeAnimator mAnim;
241
242 public Board(Context context, AttributeSet as) {
243 super(context, as);
244
245 setBackgroundColor(0xFF000000);
246
247 LauncherApplication app = (LauncherApplication)context.getApplicationContext();
248 mIcons = app.getIconCache().getAllIcons();
249 mComponentNames = new ComponentName[mIcons.size()];
250 mComponentNames = mIcons.keySet().toArray(mComponentNames);
251 }
252
253 private void reset() {
254 removeAllViews();
255
256 final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams(
257 ViewGroup.LayoutParams.WRAP_CONTENT,
258 ViewGroup.LayoutParams.WRAP_CONTENT);
259
260 if (FIXED_STARS) {
261 for(int i=0; i<20; i++) {
262 ImageView fixedStar = new ImageView(getContext(), null);
263 fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom);
264 final float s = randfrange(0.25f, 0.75f);
265 fixedStar.setScaleX(s);
266 fixedStar.setScaleY(s);
267 fixedStar.setAlpha(0.75f);
268 addView(fixedStar, wrap);
269 fixedStar.setX(randfrange(0, getWidth()));
270 fixedStar.setY(randfrange(0, getHeight()));
271 }
272 }
273
274 for(int i=0; i<NUM_ICONS*2; i++) {
275 FlyingIcon nv = (FLYING_STARS && (i < NUM_ICONS))
276 ? new FlyingStar(getContext(), null)
277 : new FlyingIcon(getContext(), null);
278 addView(nv, wrap);
279 nv.reset();
280 }
281
282 mAnim = new TimeAnimator();
283 mAnim.setTimeListener(new TimeAnimator.TimeListener() {
284 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
285 // setRotation(totalTime * 0.01f); // not as cool as you would think
286
287 final int START_ZOOM_TIME = 3000;
288 if (totalTime < START_ZOOM_TIME) {
289 final float x = totalTime/(float)START_ZOOM_TIME;
290 final float s = 1f-(float)Math.pow(x-1, 4);
291 setScaleX(s); setScaleY(s);
292 } else {
293 setScaleX(1.0f); setScaleY(1.0f);
294 }
295
296 if (mManeuveringThrusters) {
297 if (mSpeedScale > MANEUVERING_THRUST_SCALE) {
298 mSpeedScale -= (2*deltaTime/1000f);
299 }
300 if (mSpeedScale < MANEUVERING_THRUST_SCALE) {
301 mSpeedScale = MANEUVERING_THRUST_SCALE;
302 }
303 } else {
304 if (mSpeedScale < 1.0f) {
305 mSpeedScale += (deltaTime/1000f);
306 }
307 if (mSpeedScale > 1.0f) {
308 mSpeedScale = 1.0f;
309 }
310 }
311
312 for (int i=0; i<getChildCount(); i++) {
313 View v = getChildAt(i);
314 if (!(v instanceof FlyingIcon)) continue;
315 FlyingIcon nv = (FlyingIcon) v;
316 nv.update(deltaTime / 1000f * mSpeedScale);
317 final float scaledWidth = nv.getWidth() * nv.getScaleX();
318 final float scaledHeight = nv.getHeight() * nv.getScaleY();
319 if ( nv.getX() + scaledWidth < 0
320 || nv.getX() - scaledWidth > getWidth()
321 || nv.getY() + scaledHeight < 0
322 || nv.getY() - scaledHeight > getHeight())
323 {
324 nv.reset();
325 }
326 }
327 }
328 });
329 }
330
331 @Override
332 protected void onAttachedToWindow() {
333 super.onAttachedToWindow();
334 setLayerType(View.LAYER_TYPE_HARDWARE, null);
Michael Jurka3a9fced2012-04-13 14:44:29 -0700335 setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
Daniel Sandler4e1cd232011-05-12 00:06:32 -0400336
337 reset();
338 mAnim.start();
339 }
340
341 protected void onSizeChanged (int w, int h, int oldw, int oldh) {
342 super.onSizeChanged(w,h,oldw,oldh);
343 mAnim.cancel();
344 reset();
345 mAnim.start();
346 }
347
348
349 @Override
350 protected void onDetachedFromWindow() {
351 super.onDetachedFromWindow();
352 mAnim.cancel();
353 }
354
355 @Override
356 public boolean isOpaque() {
357 return true;
358 }
359
360 @Override
361 public boolean onInterceptTouchEvent(MotionEvent e) {
362 // we want to eat touch events ourselves if we're in warp speed
363 return (!(ROCKET_LAUNCHER && mManeuveringThrusters));
364 }
365
366 final Runnable mEngageWarp = new Runnable() {
367 @Override
368 public void run() {
369 mManeuveringThrusters = false;
370 }
371 };
372 public void resetWarpTimer() {
373 final Handler h = getHandler();
374 h.removeCallbacks(mEngageWarp);
375 h.postDelayed(mEngageWarp, 5000);
376 }
377
378 @Override
379 public boolean onTouchEvent(MotionEvent event) {
380 if (!ROCKET_LAUNCHER) {
381 return true;
382 }
383
384 if (event.getAction() == MotionEvent.ACTION_DOWN) {
385 if (!mManeuveringThrusters) {
386 mManeuveringThrusters = true;
387 resetWarpTimer();
388 return true;
389 }
390 }
391
392 return false;
393 }
394 }
395
396 @Override
397 public void onStart() {
398 super.onStart();
399
400 DisplayMetrics metrics = new DisplayMetrics();
401 getWindowManager().getDefaultDisplay().getMetrics(metrics);
402 final int longside = metrics.widthPixels > metrics.heightPixels
403 ? metrics.widthPixels : metrics.heightPixels;
404
405 Board b = new Board(this, null);
406 setContentView(b, new ViewGroup.LayoutParams(longside, longside));
407 b.setX((metrics.widthPixels - longside) / 2);
408 b.setY((metrics.heightPixels - longside) / 2);
409 }
410
411 @Override
412 public void onUserInteraction() {
413 if (!ROCKET_LAUNCHER) {
414 finish();
415 }
416 }
417}