package uk.org.smithfamily.mslogger.widgets.renderers; import uk.org.smithfamily.mslogger.dashboards.DashboardView; import uk.org.smithfamily.mslogger.widgets.Indicator; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.util.Log; public abstract class Painter { protected DashboardView parent; protected final Indicator model; protected double currentValue = 0.0; protected double targetValue = 0.0; private final long FULL_SWEEP_TIME = 1000; private final double epsilon = 1e-5; private long lastPointerMoveTime = 0; private int dirtyCount = 0; private final RenderStats stats = new RenderStats(); protected float centerX; protected float centerY; protected float scaleX; protected float scaleY; protected float angle; protected float left; protected float top; protected float right; protected float bottom; public Painter(final DashboardView parent, final Indicator model, final Context c) { this.parent = parent; this.model = model; init(c); lastPointerMoveTime = 0; } public void setTargetValue(final double value) { targetValue = value; } protected Resources getResources() { return parent.getResources(); } protected abstract void init(Context c); public abstract void renderFrame(Canvas canvas); public boolean updateAnimation() { boolean isDirty = false; if (offTarget()) { isDirty = true; dirtyCount++; final double range = model.getMax() - model.getMin(); final double incPerMilli = range / FULL_SWEEP_TIME; final long currentTime = System.currentTimeMillis(); if (lastPointerMoveTime == 0) { lastPointerMoveTime = currentTime; } long delay = currentTime - lastPointerMoveTime; if (delay == 0) { delay = 1; } final double direction = Math.signum(targetValue - currentValue); final double delta = Math.abs(targetValue - currentValue); final double increment = Math.min(delta, delay * incPerMilli); currentValue += (increment * direction); lastPointerMoveTime = System.currentTimeMillis(); stats.updateStats(delay, delta); if (!offTarget()) { // We are done lastPointerMoveTime = 0; stats.updateDirtyCount(dirtyCount); dirtyCount = 0; } } return isDirty; } public boolean offTarget() { return Math.abs(targetValue - currentValue) > epsilon; } public abstract Indicator.DisplayType getType(); /** * * @return */ protected int getFgColour() { int c = Color.WHITE; if (model.isDisabled()) { return Color.DKGRAY; } final double value = model.getValue(); final double lowWarning = model.getLowW(); final double highWarning = model.getHiW(); final double lowDanger = model.getLowD(); final double hiDanger = model.getHiD(); if ((value > lowWarning) && (value < highWarning)) { return Color.WHITE; } else if ((value <= lowWarning) || (value >= highWarning)) { c = Color.YELLOW; } if ((value <= lowDanger) || (value >= hiDanger)) { c = Color.RED; } return c; } /** Set the position and scale of an image in screen coordinates */ public boolean setPos(final float newleft, final float newtop, final float newright, final float newbottom, final float centerX, final float centerY, final float scaleX, final float scaleY, final float angle) { // If the size has changed, we need to flush any stored bitmaps if ((scaleX != this.scaleX) || (scaleY != this.scaleY)) { invalidateCaches(); } this.centerX = centerX; this.centerY = centerY; this.scaleX = scaleX; this.scaleY = scaleY; this.angle = angle; this.left = newleft; this.top = newtop; this.right = newright; this.bottom = newbottom; normaliseDimensions(); return true; } /** * Override this if a painter subclass has constrained dimensions (i.e. a gauge has the same width/height) */ protected void normaliseDimensions() { if (isIsotropic()) { float width = right - left; float height = bottom - top; width = Math.min(width, height); height = width; bottom = top + height; right = left + width; } } /** * Override this to clear out any cached, size dependent bitmaps */ protected void invalidateCaches() { } public float getWidth() { return right - left; } public float getHeight() { return bottom - top; } public boolean isIsotropic() { return true; } } class RenderStats { private static final long STATS_DELAY = 1000; private static final boolean LOG_PAINTER_STATS = false; int minDirty = 0; int maxDirty = 0; float avgDirty = 0; int dCounter = 0; int sCounter = 0; long minDelay = 0; long maxDelay = 0; float avgDelay = 0; double minDelta = +1e100; double maxDelta = -1e100; double avgDelta = 0; long lastOutput = System.currentTimeMillis(); @SuppressWarnings("unused") public void updateStats(final long delay, final double delta) { sCounter++; minDelay = Math.min(minDelay, delay); maxDelay = Math.max(maxDelay, delay); avgDelay = avgDelay + ((delay - avgDelay) / sCounter); minDelta = Math.min(minDelta, delta); maxDelta = Math.max(maxDelta, delta); avgDelta = avgDelta + ((delta - avgDelta) / sCounter); final long now = System.currentTimeMillis(); if (LOG_PAINTER_STATS && ((now - lastOutput) > STATS_DELAY)) { lastOutput = now; Log.i("RenderStats", String.format("minDelay=%d maxDelay=%d avgDelay=%.2f minDelta=%.2f maxDelta=%.2f avgDelta=%.2f minDirty=%d maxDirty=%d avgDirty=%.2f", minDelay, maxDelay, avgDelay, minDelta, maxDelta, avgDelta, minDirty, maxDirty, avgDirty)); } } public void updateDirtyCount(final int dirtyCount) { minDirty = Math.min(minDirty, dirtyCount); maxDirty = Math.max(maxDirty, dirtyCount); avgDirty = avgDirty + ((dirtyCount - avgDirty) / (++dCounter)); } }