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