1 /* 2 * Copyright (C) 2009 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 package com.cooliris.media; 18 19 import java.util.ArrayList; 20 21 import javax.microedition.khronos.opengles.GL11; 22 23 import android.graphics.Typeface; 24 import android.view.MotionEvent; 25 26 import com.cooliris.app.App; 27 import com.cooliris.app.Res; 28 29 public final class PathBarLayer extends Layer { 30 private static final StringTexture.Config sPathFormat = new StringTexture.Config(); 31 private final ArrayList<Component> mComponents = new ArrayList<Component>(); 32 private static final int FILL = Res.drawable.pathbar_bg; 33 private static final int JOIN = Res.drawable.pathbar_join; 34 private static final int CAP = Res.drawable.pathbar_cap; 35 private Component mTouchItem = null; 36 37 static { 38 sPathFormat.fontSize = 18f * App.PIXEL_DENSITY; 39 } 40 41 public PathBarLayer() { 42 } 43 44 public void pushLabel(int icon, String label, Runnable action) { 45 synchronized (mComponents) { 46 mComponents.add(new Component(icon, label, action, 0)); 47 } 48 recomputeComponents(); 49 } 50 51 public void setAnimatedIcons(final int[] icons) { 52 synchronized (mComponents) { 53 final int numComponents = mComponents.size(); 54 for (int i = 0; i < numComponents; ++i) { 55 final Component component = mComponents.get(i); 56 if (component != null) { 57 if (component.animatedIcons != null) { 58 component.animatedIcons = null; 59 } 60 if (i == numComponents - 1) { 61 component.animatedIcons = icons; 62 } 63 } 64 } 65 } 66 } 67 68 public void changeLabel(String label) { 69 if (label == null || label.length() == 0) 70 return; 71 Component component = popLabel(); 72 if (component != null) { 73 pushLabel(component.icon, label, component.action); 74 } 75 } 76 77 public String getCurrentLabel() { 78 final ArrayList<Component> components = mComponents; 79 synchronized (components) { 80 int lastIndex = components.size() - 1; 81 if (lastIndex < 0) { 82 return ""; 83 } 84 Component retVal = components.get(lastIndex); 85 return retVal.origString; 86 } 87 } 88 89 public Component popLabel() { 90 final ArrayList<Component> components = mComponents; 91 synchronized (components) { 92 int lastIndex = components.size() - 1; 93 if (lastIndex < 0) { 94 return null; 95 } 96 Component retVal = components.get(lastIndex); 97 components.remove(lastIndex); 98 return retVal; 99 } 100 } 101 102 private static final class Component { 103 public String origString; 104 public int icon; 105 public Runnable action; 106 public StringTexture texture; 107 public float width; 108 public float animWidth; 109 public float x; 110 public int[] animatedIcons; 111 public float timeElapsed; 112 private static final float ICON_WIDTH = 38.0f; 113 114 Component(int icon, String label, Runnable action, float widthLeft) { 115 this.action = action; 116 origString = label; 117 this.icon = icon; 118 computeLabel(widthLeft); 119 } 120 121 public final void computeLabel(float widthLeft) { 122 Typeface typeface = sPathFormat.bold ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT; 123 String label = ""; 124 if (origString != null) { 125 label = origString.substring(0, StringTexture.lengthToFit(sPathFormat.fontSize, widthLeft, typeface, origString)); 126 if (label.length() != origString.length()) { 127 label += "..."; 128 } 129 } 130 this.texture = new StringTexture(label, sPathFormat); 131 } 132 133 public final boolean update(float timeElapsed) { 134 this.timeElapsed += timeElapsed; 135 if (animWidth == 0.0f) { 136 animWidth = width; 137 } 138 animWidth = FloatUtils.animate(animWidth, width, timeElapsed); 139 if (animatedIcons != null && animatedIcons.length > 1) 140 return true; 141 if (animWidth == width) { 142 return false; 143 } else { 144 return true; 145 } 146 } 147 148 public float getIconWidth() { 149 return ICON_WIDTH * App.PIXEL_DENSITY; 150 } 151 } 152 153 @Override 154 public void generate(RenderView view, RenderView.Lists lists) { 155 lists.blendedList.add(this); 156 lists.hitTestList.add(this); 157 lists.updateList.add(this); 158 } 159 160 private void layout() { 161 synchronized (mComponents) { 162 int numComponents = mComponents.size(); 163 for (int i = 0; i < numComponents; ++i) { 164 Component component = mComponents.get(i); 165 if (component == null) 166 continue; 167 float iconWidth = (component.icon == 0) ? 0 : component.getIconWidth(); 168 if (iconWidth == 0) { 169 iconWidth = 8 * App.PIXEL_DENSITY; 170 } 171 float offset = 5 * App.PIXEL_DENSITY; 172 float thisComponentWidth = (i != numComponents - 1) ? iconWidth + offset : component.texture.computeTextWidth() 173 + iconWidth + offset; 174 component.width = thisComponentWidth; 175 } 176 } 177 } 178 179 @Override 180 public boolean update(RenderView view, float timeElapsed) { 181 layout(); 182 boolean retVal = false; 183 synchronized (mComponents) { 184 int numComponents = mComponents.size(); 185 for (int i = 0; i < numComponents; i++) { 186 Component component = mComponents.get(i); 187 retVal |= component.update(timeElapsed); 188 } 189 } 190 return retVal; 191 } 192 193 @Override 194 public void renderBlended(RenderView view, GL11 gl) { 195 // Draw components. 196 final Texture fill = view.getResource(FILL); 197 final Texture join = view.getResource(JOIN); 198 final Texture cap = view.getResource(CAP); 199 final float y = mY + 3; 200 int x = (int) (3 * App.PIXEL_DENSITY); 201 float height = mHeight; 202 synchronized (mComponents) { 203 int numComponents = mComponents.size(); 204 for (int i = 0; i < numComponents; ++i) { 205 Component component = mComponents.get(i); 206 component.x = x; 207 // Draw the left join if not the first component, and the fill. 208 // TODO: Draw the pressed background for mTouchItem. 209 final int width = (int) component.animWidth; 210 if (i != 0) { 211 view.draw2D(join, x - join.getWidth(), y); 212 if (view.bind(fill)) { 213 view.draw2D(x, y, 0f, width, height); 214 } 215 } else if (view.bind(fill)) { 216 view.draw2D(0f, y, 0f, x + width, height); 217 } 218 219 if (i == numComponents - 1) { 220 // Draw the cap on the right edge. 221 view.draw2D(cap, x + width, y); 222 } 223 float xOffset = 5 * App.PIXEL_DENSITY; 224 // Draw the label. 225 final int[] icons = component.animatedIcons; 226 227 // Cycles animated icons. 228 final int iconId = (icons != null && icons.length > 0) ? icons[(int) (component.timeElapsed * 20.0f) % icons.length] 229 : component.icon; 230 final Texture icon = view.getResource(iconId); 231 if (icon != null) { 232 view.loadTexture(icon); 233 view.draw2D(icon, x + xOffset, y - 2 * App.PIXEL_DENSITY); 234 } 235 if (i == numComponents - 1) { 236 final StringTexture texture = component.texture; 237 view.loadTexture(texture); 238 float iconWidth = component.getIconWidth(); 239 if (texture.computeTextWidth() <= (width - iconWidth)) { 240 float textOffset = (iconWidth == 0) ? 8 * App.PIXEL_DENSITY : iconWidth; 241 view.draw2D(texture, x + textOffset, y + 5); 242 } 243 } 244 x += (int) (width + (21 * App.PIXEL_DENSITY + 0.5f)); 245 } 246 } 247 } 248 249 private Component hitTestItems(float x, float y) { 250 if (y >= mY && y < mY + mHeight) { 251 synchronized (mComponents) { 252 int numComponents = mComponents.size(); 253 for (int i = 0; i < numComponents; i++) { 254 final Component component = mComponents.get(i); 255 float componentx = component.x; 256 if (x >= componentx && x < componentx + component.width) { 257 return component; 258 } 259 } 260 } 261 } 262 return null; 263 } 264 265 @Override 266 public boolean onTouchEvent(MotionEvent event) { 267 final float x = event.getX(); 268 final float y = event.getY(); 269 switch (event.getAction()) { 270 case MotionEvent.ACTION_DOWN: 271 mTouchItem = hitTestItems(x, y); 272 break; 273 case MotionEvent.ACTION_MOVE: 274 break; 275 case MotionEvent.ACTION_UP: 276 if (mTouchItem != null) { 277 mTouchItem.action.run(); 278 } 279 case MotionEvent.ACTION_CANCEL: 280 mTouchItem = null; 281 break; 282 } 283 return true; 284 } 285 286 public void recomputeComponents() { 287 float width = mWidth; 288 width -= 20f * App.PIXEL_DENSITY; 289 synchronized (mComponents) { 290 int numComponents = mComponents.size(); 291 for (int i = 0; i < numComponents; i++) { 292 Component component = mComponents.get(i); 293 width -= (component.getIconWidth() + 20.0f * App.PIXEL_DENSITY); 294 component.computeLabel(width); 295 } 296 } 297 } 298 299 public int getNumLevels() { 300 synchronized (mComponents) { 301 return mComponents.size(); 302 } 303 } 304 305 public void clear() { 306 synchronized (mComponents) { 307 mComponents.clear(); 308 } 309 } 310 } 311