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