com.cosmicsubspace.nerdyaudio.visuals.PlayControlsView.java Source code

Java tutorial

Introduction

Here is the source code for com.cosmicsubspace.nerdyaudio.visuals.PlayControlsView.java

Source

//Licensed under the MIT License.
//Include the license text thingy if you're gonna use this.
//Copyright (c) 2016 Chansol Yang

package com.cosmicsubspace.nerdyaudio.visuals;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.cosmicsubspace.nerdyaudio.R;
import com.cosmicsubspace.nerdyaudio.animation.EasingEquations;
import com.cosmicsubspace.nerdyaudio.animation.MixNode;
import com.cosmicsubspace.nerdyaudio.animation.PointsCompound;
import com.cosmicsubspace.nerdyaudio.animation.PropertySet;
import com.cosmicsubspace.nerdyaudio.audio.AudioPlayer;
import com.cosmicsubspace.nerdyaudio.audio.Waveform;
import com.cosmicsubspace.nerdyaudio.draw.AnimatableRectF;
import com.cosmicsubspace.nerdyaudio.draw.AnimatableShape;
import com.cosmicsubspace.nerdyaudio.draw.AnimatableText;
import com.cosmicsubspace.nerdyaudio.draw.AnimatableWaveform;
import com.cosmicsubspace.nerdyaudio.draw.PrimitivePaths;
import com.cosmicsubspace.nerdyaudio.file.MusicInformation;
import com.cosmicsubspace.nerdyaudio.file.QueueManager;
import com.cosmicsubspace.nerdyaudio.helper.BitmapConversions;
import com.cosmicsubspace.nerdyaudio.helper.ColorFiddler;
import com.cosmicsubspace.nerdyaudio.interfaces.ProgressStringListener;
import com.cosmicsubspace.nerdyaudio.interfaces.QueueListener;

//TODO : Enlarge time when scrubbing.

public class PlayControlsView extends View implements ProgressStringListener, QueueListener {
    int w, h;
    Waveform wf;
    Paint pt;
    //RectF artBounds;
    AnimatableRectF artBoundsAnim;
    MixNode<PropertySet> artBoundsNormal;
    MixNode<PropertySet> artBoundsExpanded;

    int menuColor, buttonColor, textPrimary, textSecondary;
    int timestampColor = Color.WHITE, timestampBackgroundColor = Color.BLACK;
    int playedColorEX = Color.BLACK, remainingColorEX = Color.GRAY, playedColor = Color.BLACK,
            remainingColor = Color.GRAY;
    int timestampSize = 24;
    int buttonPaddings = 16;
    int buttonMargins = 8;//dp
    int albumArtSize = 100;//dp
    int normalBarHeight = 100; //dp
    int expandedBarHeight = 200;
    int buttonsSize = 36;//dp
    int albumArtMargin = 12;//dp
    float spacing = 0.3f;
    float currentPosition = 0.5f;
    float timestampOffsetX = 30, timestampOffsetY = 100;
    AudioPlayer ap;
    QueueManager qm;
    boolean displayTimeStamp = true;
    public static final String LOG_TAG = "CS_AFN";
    float density;

    AnimatableShape playBtn;
    MixNode<PointsCompound> playBtnShape;
    MixNode<PointsCompound> playBtnShapeNormal;
    MixNode<PointsCompound> playBtnShapeDiamond;
    MixNode<PointsCompound> playBtnShapeExpanded;

    MixNode<PropertySet> buttonFollower;
    MixNode<PropertySet> buttonFollowerProgress;
    MixNode<PropertySet> buttonFollowerUser;
    MixNode<PropertySet> buttonFollowerYExpanded;
    MixNode<PropertySet> buttonFollowerYNotExpanded;

    MixNode<PropertySet> playBtnRestPosition;
    MixNode<PropertySet> playBtnRestCenter;
    MixNode<PropertySet> playBtnRestSide;

    AnimatableText titleAnimatable;
    MixNode<PropertySet> titleNoArt;
    MixNode<PropertySet> titleNormal;
    MixNode<PropertySet> titleExpanded;
    MixNode<PropertySet> titleNotExpanded;

    AnimatableText artistAnimatable;
    MixNode<PropertySet> artistNoArt;
    MixNode<PropertySet> artistNormal;
    MixNode<PropertySet> artistExpanded;
    MixNode<PropertySet> artistNotExpanded;

    MixNode<PropertySet> albumArtColor;
    MixNode<PropertySet> albumArtNormal;
    MixNode<PropertySet> albumArtNone;

    AnimatableShape pauseBtn;
    MixNode<PointsCompound> pausePointsMixed;
    MixNode<PointsCompound> pausePointsSquare;
    MixNode<PointsCompound> pausePointsNormal;
    MixNode<PropertySet> pauseRest;
    MixNode<PropertySet> pauseRestCenter;
    MixNode<PropertySet> pauseRestSide;
    MixNode<PropertySet> pauseVisible;
    MixNode<PropertySet> pauseInvisible;

    AnimatableShape nextBtn;
    MixNode<PropertySet> nextSide;
    MixNode<PropertySet> nextCenter;
    MixNode<PropertySet> nextBounce;

    AnimatableShape prevBtn;
    MixNode<PropertySet> prevSide;
    MixNode<PropertySet> prevCenter;
    MixNode<PropertySet> prevBounce;

    AnimatableText timestampAnim;
    MixNode<PropertySet> timestampRest;
    MixNode<PropertySet> timestampFollow;
    MixNode<PropertySet> timestampExpanded;
    MixNode<PropertySet> timestampNotExpanded;

    AnimatableWaveform waveform;
    MixNode<PropertySet> waveformExpanded;
    MixNode<PropertySet> waveformNotExpanded;
    MixNode<PropertySet> waveformCenterActive;
    MixNode<PropertySet> waveformCenterInactive;

    MixNode<PropertySet> barHeight;
    MixNode<PropertySet> expandedBarHeightMP;
    MixNode<PropertySet> normalBarHeightMP;

    AnimatableText filePath;
    MixNode<PropertySet> filePathActive;
    MixNode<PropertySet> filePathActiveArt;
    MixNode<PropertySet> filePathActiveNoArt;
    MixNode<PropertySet> filePathInactive;

    static PlayControlsView inst;

    public static PlayControlsView getInstance() {
        return inst;
    }

    //TODO Performance. Srsly.

    public PlayControlsView(Context context, AttributeSet attrs) {
        super(context, attrs);
        pt = new Paint(Paint.ANTI_ALIAS_FLAG);
        density = getResources().getDisplayMetrics().density;
        qm = QueueManager.getInstance();
        ap = AudioPlayer.getInstance();

        Resources res = getResources();
        menuColor = getResources().getColor(R.color.colorPrimary);
        buttonColor = Color.WHITE;
        textPrimary = getResources().getColor(R.color.colorLightPrimaryText);
        textSecondary = getResources().getColor(R.color.colorLightSecondaryText);

        playedColor = ColorFiddler.setAlpha(res.getColor(R.color.colorPrimary), 200);
        //Log2.log(2,this,Color.alpha(playedColor),Color.red(playedColor),Color.blue(playedColor),Color.green(playedColor));
        remainingColor = ColorFiddler.setAlpha(res.getColor(R.color.colorPrimary), 100);

        playedColorEX = ColorFiddler.setAlpha(res.getColor(R.color.colorAccentFallbackDark), 255);
        remainingColorEX = ColorFiddler.setAlpha(res.getColor(R.color.colorAccent), 255);
        //We need to disable hardware acceleration for this layer,
        //since we need to fiddle with blending and shader combining
        //for the marquee text effect.
        //It also takes care of a bug with Paths.
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        prepareLayout();

        inst = this;

        refreshPlaying();
    }

    public void refreshPlaying() {
        parseMusicInformation(qm.getCurrentlyPlaying());
    }

    public void setWaveform(Waveform w) {
        this.wf = w;
        invalidate();
    }

    protected void prepareLayout() {
        barHeight = new MixNode<PropertySet>("BarHeight");
        expandedBarHeightMP = new MixNode<PropertySet>("Expanded",
                new PropertySet().setValue("Height", expandedBarHeight * density));
        expandedBarHeightMP.getInfluence().set(0.0f);
        normalBarHeightMP = new MixNode<PropertySet>("Expanded",
                new PropertySet().setValue("Height", normalBarHeight * density));
        barHeight.addNode(expandedBarHeightMP);
        barHeight.addNode(normalBarHeightMP);

        float buttonsAreaW = w - albumArtSize * density;
        float buttonsAreaIni = albumArtSize * density;
        float halfButtonPx = buttonsSize * density / 2.0f;
        //prevBtnBounds = new RectF(buttonsAreaIni + buttonsAreaW * (1.0f / 6.0f) - halfButtonPx, h - 16 * density - halfButtonPx,
        //        buttonsAreaIni + buttonsAreaW * (1.0f / 6.0f) + halfButtonPx, h - 16 * density + halfButtonPx);
        //playBtnBounds = new RectF(buttonsAreaIni + buttonsAreaW * (3.0f / 6.0f) - halfButtonPx, h - 16 * density - halfButtonPx,
        //        buttonsAreaIni + buttonsAreaW * (3.0f / 6.0f) + halfButtonPx, h - 16 * density + halfButtonPx);
        //nextBtnBounds = new RectF(buttonsAreaIni + buttonsAreaW * (5.0f / 6.0f) - halfButtonPx, h - 16 * density - halfButtonPx,
        //        buttonsAreaIni + buttonsAreaW * (5.0f / 6.0f) + halfButtonPx, h - 16 * density + halfButtonPx);
        //artBounds = new RectF(albumArtMargin * density, h-(-albumArtMargin + (normalBarHeight)) * density, (albumArtSize - albumArtMargin) * density, h-(-albumArtSize + albumArtMargin + normalBarHeight) * density);
        artBoundsAnim = new AnimatableRectF(new MixNode<PropertySet>("Final"));
        artBoundsNormal = new MixNode<PropertySet>("Normal",
                new PropertySet().setValue("X-", albumArtMargin * density)
                        .setValue("Y-", h - (-albumArtMargin + (normalBarHeight)) * density)
                        .setValue("X+", (albumArtSize - albumArtMargin) * density)
                        .setValue("Y+", h - (-albumArtSize + albumArtMargin + normalBarHeight) * density));
        artBoundsExpanded = new MixNode<PropertySet>("Expanded",
                new PropertySet().setValue("X-", albumArtMargin * density)
                        .setValue("Y-", h - (-albumArtMargin + (expandedBarHeight)) * density)
                        .setValue("X+", (albumArtSize - albumArtMargin) * density)
                        .setValue("Y+", h - (-albumArtSize + albumArtMargin + expandedBarHeight) * density));
        artBoundsExpanded.getInfluence().set(0.0f);
        artBoundsAnim.getMixNode().addNode(artBoundsNormal);
        artBoundsAnim.getMixNode().addNode(artBoundsExpanded);

        buttonFollower = new MixNode<PropertySet>("Follower");
        buttonFollowerUser = new MixNode<PropertySet>("User", new PropertySet().setValue("X", 0)
                .setValue("Scale", 1.0f).setValue("Rotation", 0).setValue("Alpha", 1.0f));
        buttonFollowerProgress = new MixNode<PropertySet>("Progress", new PropertySet().setValue("X", 0)
                .setValue("Scale", 1.0f).setValue("Rotation", 0).setValue("Alpha", 1.0f));
        buttonFollowerUser.getInfluence().set(0.0f);

        buttonFollowerYExpanded = new MixNode<PropertySet>("YExpanded",
                new PropertySet().setValue("Y", h - (expandedBarHeight - albumArtSize - 20) * density));
        buttonFollowerYExpanded.getInfluence().set(0.0f);
        buttonFollowerYNotExpanded = new MixNode<PropertySet>("YNotExpanded",
                new PropertySet().setValue("Y", h - (normalBarHeight) * density));
        buttonFollower.addNode(buttonFollowerProgress);
        buttonFollower.addNode(buttonFollowerUser);
        buttonFollower.addNode(buttonFollowerYExpanded);
        buttonFollower.addNode(buttonFollowerYNotExpanded);
        buttonFollower.getInfluence().set(0);

        playBtnRestPosition = new MixNode<PropertySet>("Rest");
        playBtnRestSide = new MixNode<PropertySet>("Side",
                new PropertySet().setValue("X", buttonsAreaIni + buttonsAreaW * (3.0f / 6.0f))
                        .setValue("Y", h - (buttonsSize / 2 + buttonMargins) * density).setValue("Scale", 1)
                        .setValue("Rotation", -90).setValue("Alpha", 1.0f));
        playBtnRestCenter = new MixNode<PropertySet>("Center",
                new PropertySet().setValue("X", w * (3.0f / 6.0f))
                        .setValue("Y", h - (buttonsSize / 2 + buttonMargins) * density).setValue("Scale", 1)
                        .setValue("Rotation", -90).setValue("Alpha", 1.0f));
        playBtnRestCenter.getInfluence().set(0.0f);
        playBtnRestPosition.addNode(playBtnRestSide);
        playBtnRestPosition.addNode(playBtnRestCenter);

        playBtnShape = new MixNode<PointsCompound>("Play Btn Shape");
        playBtnShapeNormal = new MixNode<PointsCompound>("Play Btn Normal",
                PrimitivePaths.play(buttonsSize / 2.0f * density));
        playBtnShapeDiamond = new MixNode<PointsCompound>("Play Btn Diamond",
                PrimitivePaths.playDiamond(buttonsSize / 2.0f * density * 0.3f));
        playBtnShapeExpanded = new MixNode<PointsCompound>("Play Btn Expanded", PrimitivePaths
                .playExpanded(buttonsSize / 2.0f * density * 0.3f, 15 * density, 40 * density, 30 * density));
        playBtnShapeDiamond.getInfluence().set(0.0f);
        playBtnShapeExpanded.getInfluence().set(0.0f);
        playBtnShape.getInfluence().set(1.0f);
        playBtnShape.addNode(playBtnShapeNormal);
        playBtnShape.addNode(playBtnShapeDiamond);
        playBtnShape.addNode(playBtnShapeExpanded);

        playBtn = new AnimatableShape(playBtnShape, buttonColor, new MixNode<PropertySet>("Mix"));
        playBtn.getMixNode().addNode(buttonFollower);
        playBtn.getMixNode().addNode(playBtnRestPosition);

        pauseRest = new MixNode<PropertySet>("Rest");
        pauseRestSide = new MixNode<PropertySet>("Side",
                new PropertySet().setValue("X", buttonsAreaIni + buttonsAreaW * (3.0f / 6.0f)).setValue("Y",
                        h - (buttonsSize / 2 + buttonMargins) * density));
        pauseRestCenter = new MixNode<PropertySet>("Center", new PropertySet().setValue("X", w * (3.0f / 6.0f))
                .setValue("Y", h - (buttonsSize / 2 + buttonMargins) * density));
        pauseRestCenter.getInfluence().set(0.0f);
        pauseRest.addNode(pauseRestSide);
        pauseRest.addNode(pauseRestCenter);

        pausePointsMixed = new MixNode<PointsCompound>("Pause Shape");
        pausePointsNormal = new MixNode<PointsCompound>("Pause Normal",
                PrimitivePaths.pause(buttonsSize / 2.0f * density));
        pausePointsSquare = new MixNode<PointsCompound>("Pause Square",
                PrimitivePaths.pauseSquare(buttonsSize / 2.0f * density));
        pausePointsNormal.getInfluence().set(0.0f);
        pausePointsMixed.addNode(pausePointsSquare);
        pausePointsMixed.addNode(pausePointsNormal);

        pauseBtn = new AnimatableShape(pausePointsMixed, buttonColor, new MixNode<PropertySet>("Mix"));

        pauseVisible = new MixNode<PropertySet>("PauseVisible",
                new PropertySet().setValue("Alpha", 1.0f).setValue("Scale", 1).setValue("Rotation", 0));
        pauseVisible.getInfluence().set(0);
        pauseInvisible = new MixNode<PropertySet>("PauseInvisible",
                new PropertySet().setValue("Alpha", 0.0f).setValue("Scale", 0).setValue("Rotation", 180));

        pauseBtn.getMixNode().addNode(pauseInvisible);
        pauseBtn.getMixNode().addNode(pauseVisible);
        pauseBtn.getMixNode().addNode(pauseRest);

        nextBtn = new AnimatableShape(PrimitivePaths.next(buttonsSize / 2.0f * density), buttonColor,
                new MixNode<PropertySet>("FinalMix"));
        nextSide = new MixNode<PropertySet>("Side",
                new PropertySet().setValue("X", buttonsAreaIni + buttonsAreaW * (5.0f / 6.0f))
                        .setValue("Y", h - (buttonsSize / 2 + buttonMargins) * density).setValue("Scale", 1)
                        .setValue("Rotation", 0).setValue("Alpha", 1.0f));
        nextCenter = new MixNode<PropertySet>("Center",
                new PropertySet().setValue("X", w * (5.0f / 6.0f))
                        .setValue("Y", h - (buttonsSize / 2 + buttonMargins) * density).setValue("Scale", 1)
                        .setValue("Rotation", 0).setValue("Alpha", 1.0f));
        nextCenter.getInfluence().set(0.0f);
        nextBtn.getMixNode().addNode(nextSide);
        nextBtn.getMixNode().addNode(nextCenter);
        nextBounce = new MixNode<PropertySet>("Next Bounce", new PropertySet().setValue("X", 32 * density));
        nextBounce.getBasis().setMode(PropertySet.ADDITIVE);
        nextBounce.getInfluence().set(0.0f);
        nextBtn.getMixNode().addNode(nextBounce);

        prevBtn = new AnimatableShape(PrimitivePaths.next(buttonsSize / 2.0f * density), buttonColor,
                new MixNode<PropertySet>("FinalMix"));
        prevSide = new MixNode<PropertySet>("Side",
                new PropertySet().setValue("X", buttonsAreaIni + buttonsAreaW * (1.0f / 6.0f))
                        .setValue("Y", h - (buttonsSize / 2 + buttonMargins) * density).setValue("Scale", 1)
                        .setValue("Rotation", 180).setValue("Alpha", 1.0f));
        prevCenter = new MixNode<PropertySet>("Center",
                new PropertySet().setValue("X", w * (1.0f / 6.0f))
                        .setValue("Y", h - (buttonsSize / 2 + buttonMargins) * density).setValue("Scale", 1)
                        .setValue("Rotation", 180).setValue("Alpha", 1.0f));
        prevCenter.getInfluence().set(0.0f);
        prevBtn.getMixNode().addNode(prevSide);
        prevBtn.getMixNode().addNode(prevCenter);
        prevBounce = new MixNode<PropertySet>("Prev Bounce", new PropertySet().setValue("X", -32 * density));
        prevBounce.getBasis().setMode(PropertySet.ADDITIVE);
        prevBounce.getInfluence().set(0.0f);
        prevBtn.getMixNode().addNode(prevBounce);

        titleAnimatable = new AnimatableText(new MixNode<PropertySet>("Mix"), textPrimary, "", 24 * density);
        titleNoArt = new MixNode<PropertySet>("NoArt", new PropertySet().setValue("X", albumArtMargin * density));
        titleNoArt.getInfluence().set(0);
        titleNormal = new MixNode<PropertySet>("Normal", new PropertySet().setValue("X", albumArtSize * density));
        titleExpanded = new MixNode<PropertySet>("Expanded",
                new PropertySet().setValue("Y", h - (expandedBarHeight - 6) * density));
        titleExpanded.getInfluence().set(0.0f);
        titleNotExpanded = new MixNode<PropertySet>("NotExpanded",
                new PropertySet().setValue("Y", h - (normalBarHeight - 6) * density));
        titleAnimatable.getMixNode().addNode(titleNoArt);
        titleAnimatable.getMixNode().addNode(titleNormal);
        titleAnimatable.getMixNode().addNode(titleExpanded);
        titleAnimatable.getMixNode().addNode(titleNotExpanded);
        titleAnimatable.enableMarquee(w - (albumArtSize + albumArtMargin) * density, 16 * density);

        artistNormal = new MixNode<PropertySet>("Normal", new PropertySet().setValue("X", albumArtSize * density));
        artistAnimatable = new AnimatableText(new MixNode<PropertySet>("FinalMix"), textSecondary, "",
                16 * density);
        artistNoArt = new MixNode<PropertySet>("NoArt", new PropertySet().setValue("X", albumArtMargin * density));
        artistNoArt.getInfluence().set(0);
        artistExpanded = new MixNode<PropertySet>("Expanded",
                new PropertySet().setValue("Y", h - (expandedBarHeight - 36) * density));
        artistExpanded.getInfluence().set(0.0f);
        artistNotExpanded = new MixNode<PropertySet>("NotExpanded",
                new PropertySet().setValue("Y", h - (normalBarHeight - 36) * density));

        artistAnimatable.getMixNode().addNode(artistNoArt);
        artistAnimatable.getMixNode().addNode(artistNormal);
        artistAnimatable.getMixNode().addNode(artistExpanded);
        artistAnimatable.getMixNode().addNode(artistNotExpanded);

        filePath = new AnimatableText(new MixNode<PropertySet>("Mix"), textSecondary, "", 12 * density);
        filePath.enableMarquee(w - (albumArtSize + albumArtMargin) * density, 16 * density);
        filePathActive = new MixNode<PropertySet>("Active");
        filePathActiveArt = new MixNode<PropertySet>("Art", new PropertySet().setValue("X", albumArtSize * density)
                .setValue("Y", h - (expandedBarHeight - 60) * density));
        filePathActiveNoArt = new MixNode<PropertySet>("NoArt", new PropertySet()
                .setValue("X", albumArtMargin * density).setValue("Y", h - (expandedBarHeight - 60) * density));
        filePathActiveNoArt.getInfluence().set(0.0f);
        filePathActive.addNode(filePathActiveArt);
        filePathActive.addNode(filePathActiveNoArt);
        filePathInactive = new MixNode<PropertySet>("Inactive",
                new PropertySet().setValue("X", w).setValue("Y", h - (expandedBarHeight - 60) * density));
        filePathActive.getInfluence().set(0.0f);
        filePath.getMixNode().addNode(filePathActive);
        filePath.getMixNode().addNode(filePathInactive);

        albumArtColor = new MixNode<PropertySet>("AlbumArtMix");
        albumArtNormal = new MixNode<PropertySet>("Normal", new PropertySet().setValue("alpha", 255));
        albumArtNone = new MixNode<PropertySet>("NoArt", new PropertySet().setValue("alpha", 0));
        albumArtNone.getInfluence().set(0);
        albumArtColor.addNode(albumArtNone);
        albumArtColor.addNode(albumArtNormal);

        timestampAnim = new AnimatableText(new MixNode<PropertySet>("FinalMixed"), textPrimary, "",
                timestampSize * density);
        timestampRest = new MixNode<PropertySet>("Rest", new PropertySet().setValue("X", w - 30 * density));
        timestampFollow = new MixNode<PropertySet>("Follow", new PropertySet().setValue("X", 0));
        timestampNotExpanded = new MixNode<PropertySet>("NotExpanded",
                new PropertySet().setValue("Y", h - (normalBarHeight + 30) * density));

        timestampExpanded = new MixNode<PropertySet>("Expanded",
                new PropertySet().setValue("Y", h - (expandedBarHeight - albumArtSize + 10) * density));
        timestampExpanded.getInfluence().set(0.0f);
        timestampFollow.getInfluence().set(0.0f);
        timestampAnim.getMixNode().addNode(timestampRest);
        timestampAnim.getMixNode().addNode(timestampFollow);
        timestampAnim.getMixNode().addNode(timestampNotExpanded);
        timestampAnim.getMixNode().addNode(timestampExpanded);
        timestampAnim.setBgColor(timestampBackgroundColor);
        timestampAnim.drawBackground(true);
        timestampAnim.setAlign(AnimatableText.ALIGN_CENTER);
        //playBtn=new AnimatableShape(PrimitivePaths.triangle(50),50,50,1,0);

        waveform = new AnimatableWaveform(wf, ap, new MixNode<PropertySet>("Final"), density);
        waveformNotExpanded = new MixNode<PropertySet>("NotExpanded", new PropertySet().setValue("X", 0)
                .setValue("Y", h - normalBarHeight * density).setValue("XSize", w).setValue("YSize", 50 * density)
                .setValue("YBalance", 1.0f).setValue("Played-R", ColorFiddler.rF(playedColor))
                .setValue("Played-G", ColorFiddler.gF(playedColor))
                .setValue("Played-B", ColorFiddler.bF(playedColor))
                .setValue("Played-A", ColorFiddler.aF(playedColor))
                .setValue("Remaining-R", ColorFiddler.rF(remainingColor))
                .setValue("Remaining-G", ColorFiddler.gF(remainingColor))
                .setValue("Remaining-B", ColorFiddler.bF(remainingColor))
                .setValue("Remaining-A", ColorFiddler.aF(remainingColor)).setValue("CenterBarHeight", 2 * density));
        waveformExpanded = new MixNode<PropertySet>("Expanded",
                new PropertySet().setValue("X", 0)
                        .setValue("Y", h - (expandedBarHeight - albumArtSize - 20) * density).setValue("XSize", w)
                        .setValue("YSize", 40 * density).setValue("YBalance", 0.5f)
                        .setValue("Played-R", ColorFiddler.rF(playedColorEX))
                        .setValue("Played-G", ColorFiddler.gF(playedColorEX))
                        .setValue("Played-B", ColorFiddler.bF(playedColorEX))
                        .setValue("Played-A", ColorFiddler.aF(playedColorEX))
                        .setValue("Remaining-R", ColorFiddler.rF(remainingColorEX))
                        .setValue("Remaining-G", ColorFiddler.gF(remainingColorEX))
                        .setValue("Remaining-B", ColorFiddler.bF(remainingColorEX))
                        .setValue("Remaining-A", ColorFiddler.aF(remainingColorEX))
                        .setValue("CenterBarHeight", 2 * density));
        waveformCenterActive = new MixNode<PropertySet>("Waveform Center Active",
                new PropertySet().setValue("CenterBarWidth", 1.0f));
        waveformCenterInactive = new MixNode<PropertySet>("Waveform Center Inactive",
                new PropertySet().setValue("CenterBarWidth", 0.0f));
        waveformCenterActive.getInfluence().set(0.0f);
        waveformExpanded.getInfluence().set(0.0f);
        waveform.getMixNode().addNode(waveformExpanded);
        waveform.getMixNode().addNode(waveformNotExpanded);
        waveform.getMixNode().addNode(waveformCenterInactive);
        waveform.getMixNode().addNode(waveformCenterActive);

    }

    private void toggleExpand() {
        if (expanded) {
            expand(false);
        } else {
            expand(true);
        }
    }

    boolean expanded = false;

    public void expand(boolean expand) {
        int ease = EasingEquations.QUINTIC;
        if (this.expanded == expand)
            return;
        this.expanded = expand;
        if (expand) {
            expandedBarHeightMP.getInfluence().animate(1, 1, ease);
            normalBarHeightMP.getInfluence().animate(0, 1, ease);

            titleExpanded.getInfluence().animate(1, 1, ease);
            titleNotExpanded.getInfluence().animate(0, 1, ease);

            artistExpanded.getInfluence().animate(1, 1, ease);
            artistNotExpanded.getInfluence().animate(0, 1, ease);

            artBoundsExpanded.getInfluence().animate(1, 1, ease);
            artBoundsNormal.getInfluence().animate(0, 1, ease);

            waveformExpanded.getInfluence().animate(1, 1, ease);
            waveformNotExpanded.getInfluence().animate(0, 1, ease);

            timestampExpanded.getInfluence().animate(1, 1, ease);
            timestampNotExpanded.getInfluence().animate(0, 1, ease);

            buttonFollowerYExpanded.getInfluence().animate(1, 1, ease);
            buttonFollowerYNotExpanded.getInfluence().animate(0, 1, ease);

            filePathActive.getInfluence().animate(1, 1, ease);
            filePathInactive.getInfluence().animate(0, 1, ease);
            buttonsCenter(true, ease);
        } else {
            expandedBarHeightMP.getInfluence().animate(0, 1, ease);
            normalBarHeightMP.getInfluence().animate(1, 1, ease);

            titleExpanded.getInfluence().animate(0, 1, ease);
            titleNotExpanded.getInfluence().animate(1, 1, ease);

            artistExpanded.getInfluence().animate(0, 1, ease);
            artistNotExpanded.getInfluence().animate(1, 1, ease);

            artBoundsExpanded.getInfluence().animate(0, 1, ease);
            artBoundsNormal.getInfluence().animate(1, 1, ease);

            waveformExpanded.getInfluence().animate(0, 1, ease);
            waveformNotExpanded.getInfluence().animate(1, 1, ease);

            timestampExpanded.getInfluence().animate(0, 1, ease);
            timestampNotExpanded.getInfluence().animate(1, 1, ease);

            buttonFollowerYExpanded.getInfluence().animate(0, 1, ease);
            buttonFollowerYNotExpanded.getInfluence().animate(1, 1, ease);

            filePathActive.getInfluence().animate(0, 1, ease);
            filePathInactive.getInfluence().animate(1, 1, ease);
            if (currentMusic != null)
                if (currentMusic.hasArt())
                    buttonsCenter(false, ease);
        }
    }

    private void buttonsCenter(boolean center) {
        buttonsCenter(center, EasingEquations.DEFAULT_EASE);
    }

    private void buttonsCenter(boolean center, int ease) {
        if (!center) {
            playBtnRestCenter.getInfluence().animate(0, 1, ease);
            playBtnRestSide.getInfluence().animate(1, 1, ease);

            pauseRestCenter.getInfluence().animate(0, 1, ease);
            pauseRestSide.getInfluence().animate(1, 1, ease);

            prevCenter.getInfluence().animate(0, 1, ease);
            prevSide.getInfluence().animate(1, 1, ease);
            nextCenter.getInfluence().animate(0, 1, ease);
            nextSide.getInfluence().animate(1, 1, ease);

        } else {
            playBtnRestCenter.getInfluence().animate(1, 1, ease);
            playBtnRestSide.getInfluence().animate(0, 1, ease);
            pauseRestCenter.getInfluence().animate(1, 1, ease);
            pauseRestSide.getInfluence().animate(0, 1, ease);

            prevCenter.getInfluence().animate(1, 1, ease);
            prevSide.getInfluence().animate(0, 1, ease);
            nextCenter.getInfluence().animate(1, 1, ease);
            nextSide.getInfluence().animate(0, 1, ease);
        }
    }

    String title;
    String artist;
    Bitmap albumArt;
    MusicInformation currentMusic;

    protected void parseMusicInformation(final MusicInformation mi) {
        if (mi == null)
            return;

        currentMusic = mi;
        title = mi.getTitle();
        titleAnimatable.setText(mi.getTitle());
        artist = mi.getArtist();
        artistAnimatable.setText(mi.getArtist());
        filePath.setText(mi.getFilepath());

        if (mi.hasArt()) {
            artistNormal.getInfluence().set(0);
            artistNoArt.getInfluence().set(1);

            new Thread(new Runnable() {
                @Override
                public void run() {
                    //TODO this soometimes cause weird race condition glithches.
                    RectF bounds = artBoundsAnim.getRectF(currentFrameTime);
                    albumArt = BitmapConversions.decodeSampledBitmapFromResource(mi.getArtByteArray(),
                            Math.round(bounds.width()), Math.round(bounds.height()));
                    albumArtNormal.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
                    albumArtNone.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
                }
            }).start();

            titleNormal.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
            titleNoArt.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
            titleAnimatable.enableMarquee(w - (albumArtSize + albumArtMargin) * density, 16 * density);
            filePath.enableMarquee(w - (albumArtSize + albumArtMargin) * density, 16 * density);
            filePathActiveArt.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
            filePathActiveNoArt.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
            artistNormal.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
            artistNoArt.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

            if (!expanded)
                buttonsCenter(false);

        } else {
            titleNormal.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
            titleNoArt.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
            titleAnimatable.enableMarquee(w - albumArtMargin * 2 * density, 16 * density);
            filePath.enableMarquee(w - albumArtMargin * 2 * density, 16 * density);
            filePathActiveArt.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
            filePathActiveNoArt.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
            albumArtNormal.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
            albumArtNone.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
            artistNormal.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
            artistNoArt.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);

            if (!expanded)
                buttonsCenter(true);
        }
    }

    long currentFrameTime;

    @Override
    protected void onDraw(Canvas canvas) {

        //Log2.log(2,this,buttonFollower.getInfluence().getValue(),playBtnRestPosition.getInfluence().getValue());

        currentFrameTime = System.currentTimeMillis();

        pt.setColor(menuColor);
        canvas.drawRect(0, h - barHeight.getValue(currentFrameTime).getValue("Height"), w, h, pt);

        titleAnimatable.draw(canvas, pt, currentFrameTime);

        artistAnimatable.draw(canvas, pt, currentFrameTime);

        filePath.draw(canvas, pt, currentFrameTime);
        if (albumArt != null) {
            pt.setAlpha(Math.round(albumArtColor.getValue(currentFrameTime).getValue("alpha")));
            canvas.drawBitmap(albumArt, null, artBoundsAnim.getRectF(currentFrameTime), pt);
            //Log2.log(1,this, "trying to draw..");
            pt.setAlpha(255);
        }

        waveform.draw(canvas, pt, currentFrameTime);

        pt.setColor(Color.argb(50, 0, 0, 0));

        pt.setColor(buttonColor);

        playBtn.draw(canvas, pt, currentFrameTime);

        pauseBtn.draw(canvas, pt, currentFrameTime);

        prevBtn.draw(canvas, pt, currentFrameTime);
        nextBtn.draw(canvas, pt, currentFrameTime);

        if (wf != null && ap != null && wf.isReady() && wf.getFilename().equals(ap.getSourceString())) {
            setCurrentPosition((float) (ap.getMusicCurrentFrame() / (double) wf.getNumOfFrames()));

            if (!dragMode) {
                timestampAnim.setText(wf.frameNumberToTimeStamp(ap.getMusicCurrentFrame()));
            }

            timestampAnim.draw(canvas, pt, currentFrameTime);

            buttonFollowerProgress.getBasis().setValue("X", w * currentPosition);
        }

        invalidate();

    }

    private void animatePlay() {

        playBtnShapeDiamond.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
        playBtnShapeNormal.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

        playBtnRestPosition.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
        buttonFollower.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);

        pauseVisible.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
        pauseInvisible.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

        pausePointsSquare.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
        pausePointsNormal.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);

        waveformCenterActive.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
        waveformCenterInactive.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
    }

    private void animateStop() {
        playBtnShapeDiamond.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
        playBtnShapeNormal.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);

        playBtnRestPosition.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
        buttonFollower.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

        pauseVisible.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
        pauseInvisible.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);

        pausePointsSquare.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
        pausePointsNormal.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

        waveformCenterActive.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
        waveformCenterInactive.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
    }

    private void animateNext() {
        nextBounce.getInfluence().set(1.0f);
        nextBounce.getInfluence().animate(0.0f, 1, EasingEquations.DEFAULT_EASE);
    }

    private void animatePrev() {
        prevBounce.getInfluence().set(1.0f);
        prevBounce.getInfluence().animate(0.0f, 1, EasingEquations.DEFAULT_EASE);
    }

    float iniX, iniY;
    boolean dragMode = false;

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = MotionEventCompat.getActionMasked(ev);
        if (action == MotionEvent.ACTION_DOWN) {
            iniX = ev.getX();
            iniY = ev.getY();

            //Play Button Drag
            if (buttonFollower.getInfluence().getValue() > 0.99f) { //if in playing mode and button is in follow state
                if (playBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(), ev.getY())) {
                    dragMode = true;
                    buttonFollowerUser.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
                    buttonFollowerProgress.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

                    timestampFollow.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
                    timestampRest.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

                    //TODO not zeroing the PlayBtnShapeNormal may cause anim bugs.
                    playBtnShapeExpanded.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);
                    //playBtnShapeNormal.getInfluence().animate(0,1,EasingEquations.DEFAULT_EASE);
                    playBtnShapeDiamond.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);

                    buttonFollowerUser.getBasis().setValue("X", ev.getX());
                    timestampFollow.getBasis().setValue("X", ev.getX());
                    return true;
                }
            }

            //Other buttons
            if (prevBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(), ev.getY())) {
                return true;
            } else if (playBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(),
                    ev.getY())) {
                return true;
            } else if (pauseBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(),
                    ev.getY())) {
                return true;
            } else if (nextBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(),
                    ev.getY())) {
                return true;
            } /*else if (artBoundsAnim.getRectF(currentFrameTime).contains(ev.getX(), ev.getY())) {
              return true;
              }*/

        } else if (action == MotionEvent.ACTION_MOVE) {
            //Dragging in progress
            if (dragMode) {
                buttonFollowerUser.getBasis().setValue("X", ev.getX());
                timestampFollow.getBasis().setValue("X", ev.getX());
                timestampAnim.setText(
                        wf.frameNumberToTimeStamp((long) (wf.getNumOfFrames() * (ev.getX() / (double) w))));
            }
            return true;
        } else if (action == MotionEvent.ACTION_UP) {
            performClick();
            //buttons get priority.
            if (dragMode) {
                buttonFollowerUser.getInfluence().animate(0, 0.5f, EasingEquations.DEFAULT_EASE);
                buttonFollowerProgress.getInfluence().animate(1, 0.5f, EasingEquations.DEFAULT_EASE);

                playBtnShapeExpanded.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
                //playBtnShapeNormal.getInfluence().animate(0,1,EasingEquations.DEFAULT_EASE);
                playBtnShapeDiamond.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);

                float totalTime = (float) (wf.getNumOfFrames() / (double) ap.getSampleRate());
                ap.seekTo(totalTime * ev.getX() / w);

                timestampFollow.getInfluence().animate(0, 1, EasingEquations.DEFAULT_EASE);
                timestampRest.getInfluence().animate(1, 1, EasingEquations.DEFAULT_EASE);

                dragMode = false;

                return true;
            }

            //Button doing stuff.
            if (prevBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(), ev.getY())) {
                qm.playPreviousFile();
                return true;
            } else if (playBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(),
                    ev.getY())) {
                qm.playCurrent();
                return true;
            } else if (pauseBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(),
                    ev.getY())) {
                qm.pause();
                return true;
            } else if (nextBtn.getBounds(buttonPaddings * density, currentFrameTime).contains(ev.getX(),
                    ev.getY())) {
                qm.playNextFile();
                return true;
            } /*else if (artBoundsAnim.getRectF(currentFrameTime).contains(ev.getX(), ev.getY())) {
              toggleExpand();
                  
              return true;
              }*/

        }
        return false;
    }

    protected int rampColor(int colorA, int colorB, float ramp) {
        //Log2.log(1,this,"Ramping color..."+colorA+" | "+colorB+" | "+ramp);
        return Color.argb(Math.round(Color.alpha(colorA) * ramp + Color.alpha(colorB) * (1.0f - ramp)),
                Math.round(Color.red(colorA) * ramp + Color.red(colorB) * (1.0f - ramp)),
                Math.round(Color.green(colorA) * ramp + Color.green(colorB) * (1.0f - ramp)),
                Math.round(Color.blue(colorA) * ramp + Color.blue(colorB) * (1.0f - ramp)));

    }

    public void setSpacing(float f) {
        this.spacing = f;
    }

    public void setCurrentPosition(float f) {
        currentPosition = f;
    }

    public void setPlayedColor(int color) {
        playedColor = color;
    }

    public void setRemainingColor(int color) {
        remainingColor = color;
    }

    public void setTimestampColor(int color) {
        timestampColor = color;
    }

    public void setTimestampBackgroundColor(int color) {
        timestampBackgroundColor = color;
    }

    public void setTimestampVisibility(boolean b) {
        this.displayTimeStamp = b;
    }

    public void setTimestampSize(int x) {
        this.timestampSize = x;
    }

    public void setTimestampOffset(float x, float y) {
        this.timestampOffsetX = x;
        this.timestampOffsetY = y;
    }

    protected float getSpacingBetween() {
        if (wf != null) {
            return w / (wf.getDivisions() * (1.0f + spacing) - spacing) * (spacing + 1.0f);
        }
        return 0;
    }

    protected float getBarWidth() {
        if (wf != null) {
            return w / (wf.getDivisions() * (1.0f + spacing) - spacing);
        }
        return 0;
    }

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        this.w = w;
        this.h = h;
        prepareLayout();
    }

    @Override
    public void newSong(MusicInformation mi) {
        parseMusicInformation(mi);
    }

    @Override
    public void playbackStarted() {
        animatePlay();
    }

    @Override
    public void playbackStopped() {
        animateStop();
    }

    @Override
    public void nextSong() {
        animateNext();
    }

    @Override
    public void previousSong() {
        animatePrev();
    }

    @Override
    public void report(String s) {
        //TODO something here.
    }
}