/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.litho;
import android.annotation.TargetApi;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Build;
import com.facebook.litho.displaylist.DisplayList;
import com.facebook.litho.displaylist.DisplayListException;
/**
* Drawable which supports {@link DisplayList} for drawing and delegates all other calls to it's
* wrapped {@link android.graphics.drawable.Drawable}.
*/
class DisplayListDrawable extends Drawable implements Drawable.Callback {
private Drawable mDrawable;
private DisplayList mDisplayList;
private boolean mIgnoreInvalidations;
private boolean mInvalidated;
DisplayListDrawable(Drawable drawable, DisplayList displayList) {
setWrappedDrawable(drawable, displayList);
}
@Override
public void draw(Canvas canvas) {
if (mDisplayList == null) {
mDrawable.draw(canvas);
return;
}
try {
if (mInvalidated || !mDisplayList.isValid()) {
final Rect bounds = mDrawable.getBounds();
final Canvas displayListCanvas = mDisplayList.start(bounds.width(), bounds.height());
displayListCanvas.translate(-bounds.left, -bounds.top);
mDrawable.draw(displayListCanvas);
displayListCanvas.translate(bounds.left, bounds.top);
mDisplayList.end(displayListCanvas);
mDisplayList.setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
mInvalidated = false;
}
if (mDisplayList.isValid()) {
mDisplayList.draw(canvas);
} else {
mDrawable.draw(canvas);
}
} catch (DisplayListException e) {
// Let's make sure next draw calls will just bail the DisplayList part.
mDisplayList = null;
mDrawable.draw(canvas);
}
}
@Override
protected void onBoundsChange(Rect bounds) {
mDrawable.setBounds(bounds);
}
@Override
public void setChangingConfigurations(int configs) {
mDrawable.setChangingConfigurations(configs);
}
@Override
public int getChangingConfigurations() {
return mDrawable.getChangingConfigurations();
}
@Override
public void setDither(boolean dither) {
mDrawable.setDither(dither);
}
@Override
public void setFilterBitmap(boolean filter) {
mDrawable.setFilterBitmap(filter);
}
@Override
public void setAlpha(int alpha) {
mDrawable.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mDrawable.setColorFilter(cf);
}
@Override
public boolean isStateful() {
return mDrawable.isStateful();
}
@Override
public boolean setState(final int[] stateSet) {
return mDrawable.setState(stateSet);
}
@Override
public int[] getState() {
return mDrawable.getState();
}
@Override
public Drawable getCurrent() {
return mDrawable.getCurrent();
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart);
}
@Override
public int getOpacity() {
return mDrawable.getOpacity();
}
@Override
public Region getTransparentRegion() {
return mDrawable.getTransparentRegion();
}
@Override
public int getIntrinsicWidth() {
return mDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mDrawable.getIntrinsicHeight();
}
@Override
public int getMinimumWidth() {
return mDrawable.getMinimumWidth();
}
@Override
public int getMinimumHeight() {
return mDrawable.getMinimumHeight();
}
@Override
public boolean getPadding(Rect padding) {
return mDrawable.getPadding(padding);
}
@Override
public Drawable mutate() {
Drawable wrapped = mDrawable;
Drawable mutated = wrapped.mutate();
if (mutated != wrapped) {
// If mutate() returned a new instance, update our reference
setWrappedDrawable(mutated, mDisplayList);
}
// We return ourselves, since only the wrapped drawable needs to mutate
return this;
}
@Override
public void invalidateDrawable(Drawable who) {
invalidateSelf();
if (mIgnoreInvalidations) {
return;
}
// We need to make sure at this point that the bounds of the {@link DisplayListDrawable} are
// equals to the bounds of its content to make sure that the view invalidation works as
// expected.
setBounds(mDrawable.getBounds());
mInvalidated = true;
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
unscheduleSelf(what);
}
@Override
protected boolean onLevelChange(int level) {
return mDrawable.setLevel(level);
}
@Override
public void setTint(int tint) {
setTintList(ColorStateList.valueOf(tint));
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void setTintList(ColorStateList tint) {
mDrawable.setTintList(tint);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void setTintMode(PorterDuff.Mode tintMode) {
mDrawable.setTintMode(tintMode);
}
/**
* Sets the current wrapped {@link Drawable}
*/
void setWrappedDrawable(Drawable drawable, DisplayList displayList) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
drawable.setCallback(this);
}
mDisplayList = displayList;
invalidateSelf();
}
public void suppressInvalidations(boolean suppress) {
mIgnoreInvalidations = suppress;
}
public void release() {
setCallback(null);
mIgnoreInvalidations = false;
mDisplayList = null;
mDrawable.setCallback(null);
mDrawable = null;
}
}