diff options
Diffstat (limited to 'mobile/android/thirdparty/org/lucasr/dspec/DesignSpec.java')
-rw-r--r-- | mobile/android/thirdparty/org/lucasr/dspec/DesignSpec.java | 645 |
1 files changed, 645 insertions, 0 deletions
diff --git a/mobile/android/thirdparty/org/lucasr/dspec/DesignSpec.java b/mobile/android/thirdparty/org/lucasr/dspec/DesignSpec.java new file mode 100644 index 000000000..43505be4e --- /dev/null +++ b/mobile/android/thirdparty/org/lucasr/dspec/DesignSpec.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) 2014 Lucas Rocha + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.lucasr.dspec; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Draw a baseline grid, keylines, and spacing markers on top of a {@link View}. + * + * A {@link DesignSpec} can be configure programmatically as follows: + * <ol> + * <li>Toggle baseline grid visibility with {@link #setBaselineGridVisible(boolean)}.</li> + * <li>Change baseline grid cell width with {@link #setBaselineGridCellSize(float)}. + * <li>Change baseline grid color with {@link #setBaselineGridColor(int)}. + * <li>Toggle keylines visibility with {@link #setKeylinesVisible(boolean)}. + * <li>Change keylines color with {@link #setKeylinesColor(int)}. + * <li>Add keylines with {@link #addKeyline(float, From)}. + * <li>Toggle spacings visibility with {@link #setSpacingsVisible(boolean)}. + * <li>Change spacings color with {@link #setSpacingsColor(int)}. + * <li>Add spacing with {@link #addSpacing(float, float, From)}. + * </ol> + * + * You can also define a {@link DesignSpec} via a raw JSON resource as follows: + * <pre> + * { + * "baselineGridVisible": true, + * "baselineGridCellSize": 8, + * "keylines": [ + * { "offset": 16, + * "from": "LEFT" }, + * { "offset": 72, + * "from": "LEFT" }, + * { "offset": 16, + * "from": "RIGHT" } + * ], + * "spacings": [ + * { "offset": 0, + * "size": 16, + * "from": "LEFT" }, + * { "offset": 56, + * "size": 16, + * "from": "LEFT" }, + * { "offset": 0, + * "size": 16, + * "from": "RIGHT" } + * ] + * } + * </pre> + * + * The {@link From} arguments implicitly define the orientation of the given + * keyline or spacing i.e. {@link From#LEFT}, {@link From#RIGHT}, {@link From#HORIZONTAL_CENTER} + * are implicitly vertical; and {@link From#TOP}, {@link From#BOTTOM}, {@link From#VERTICAL_CENTER} + * are implicitly horizontal. + * + * The {@link From} arguments also define the 'direction' of the offsets and sizes in keylines and + * spacings. For example, a keyline using {@link From#RIGHT} will have its offset measured from + * right to left of the target {@link View}. + * + * The easiest way to use a {@link DesignSpec} is by enclosing your target {@link View} with + * a {@link DesignSpecFrameLayout} using the {@code designSpec} attribute as follows: + * <pre> + * <org.lucasr.dspec.DesignSpecFrameLayout + * xmlns:android="http://schemas.android.com/apk/res/android" + * android:id="@+id/design_spec" + * android:layout_width="match_parent" + * android:layout_height="match_parent" + * app:designSpec="@raw/my_spec"> + * + * ... + * + * </org.lucasr.dspec.DesignSpecFrameLayout> + * </pre> + * + * Where {@code @raw/my_spec} is a raw JSON resource. Because the {@link DesignSpec} is + * defined in an Android resource, you can vary it according to the target form factor using + * well-known resource qualifiers making it easy to define different specs for phones and tablets. + * + * Because {@link DesignSpec} is a {@link Drawable}, you can simply add it to any + * {@link android.view.ViewOverlay} if you're running your app on API level >= 18: + * + * <pre> + * DesignSpec designSpec = DesignSpec.fromResource(someView, R.raw.some_spec); + * someView.getOverlay().add(designSpec); + * </pre> + * + * @see DesignSpecFrameLayout + * @see #fromResource(View, int) + */ +public class DesignSpec extends Drawable { + private static final boolean DEFAULT_BASELINE_GRID_VISIBLE = false; + private static final boolean DEFAULT_KEYLINES_VISIBLE = true; + private static final boolean DEFAULT_SPACINGS_VISIBLE = true; + + private static final int DEFAULT_BASELINE_GRID_CELL_SIZE_DIP = 8; + + private static final String DEFAULT_BASELINE_GRID_COLOR = "#44C2185B"; + private static final String DEFAULT_KEYLINE_COLOR = "#CCC2185B"; + private static final String DEFAULT_SPACING_COLOR = "#CC89FDFD"; + + private static final float KEYLINE_STROKE_WIDTH_DIP = 1.1f; + + private static final String JSON_KEY_BASELINE_GRID_VISIBLE = "baselineGridVisible"; + private static final String JSON_KEY_BASELINE_GRID_CELL_SIZE = "baselineGridCellSize"; + private static final String JSON_KEY_BASELINE_GRID_COLOR = "baselineGridColor"; + + private static final String JSON_KEY_KEYLINES_VISIBLE = "keylinesVisible"; + private static final String JSON_KEY_KEYLINES_COLOR = "keylinesColor"; + private static final String JSON_KEY_KEYLINES = "keylines"; + + private static final String JSON_KEY_OFFSET = "offset"; + private static final String JSON_KEY_SIZE = "size"; + private static final String JSON_KEY_FROM = "from"; + + private static final String JSON_KEY_SPACINGS_VISIBLE = "spacingsVisible"; + private static final String JSON_KEY_SPACINGS_COLOR = "spacingsColor"; + private static final String JSON_KEY_SPACINGS = "spacings"; + + /** + * Defined the reference point from which keyline/spacing offsets and sizes + * will be calculated. + */ + public enum From { + LEFT, + RIGHT, + TOP, + BOTTOM, + VERTICAL_CENTER, + HORIZONTAL_CENTER + } + + private static class Keyline { + public final float position; + public final From from; + + public Keyline(float position, From from) { + this.position = position; + this.from = from; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Keyline)) { + return false; + } + + if (o == this) { + return true; + } + + final Keyline other = (Keyline) o; + return (this.position == other.position && this.from == other.from); + } + } + + private static class Spacing { + public final float offset; + public final float size; + public final From from; + + public Spacing(float offset, float size, From from) { + this.offset = offset; + this.size = size; + this.from = from; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Keyline)) { + return false; + } + + if (o == this) { + return true; + } + + final Spacing other = (Spacing) o; + return (this.offset == other.offset && + this.size == other.size && + this.from == other.from); + } + } + + private final View mHostView; + + private final float mDensity; + + private boolean mBaselineGridVisible = DEFAULT_BASELINE_GRID_VISIBLE; + private float mBaselineGridCellSize; + private final Paint mBaselineGridPaint; + + private boolean mKeylinesVisible = DEFAULT_KEYLINES_VISIBLE; + private final Paint mKeylinesPaint; + private final List<Keyline> mKeylines; + + private boolean mSpacingsVisible = DEFAULT_SPACINGS_VISIBLE; + private final Paint mSpacingsPaint; + private final List<Spacing> mSpacings; + + public DesignSpec(Resources resources, View hostView) { + mHostView = hostView; + mDensity = resources.getDisplayMetrics().density; + + mKeylines = new ArrayList<Keyline>(); + mSpacings = new ArrayList<Spacing>(); + + mBaselineGridPaint = new Paint(); + mBaselineGridPaint.setColor(Color.parseColor(DEFAULT_BASELINE_GRID_COLOR)); + + mKeylinesPaint = new Paint(); + mKeylinesPaint.setStrokeWidth(KEYLINE_STROKE_WIDTH_DIP * mDensity); + mKeylinesPaint.setColor(Color.parseColor(DEFAULT_KEYLINE_COLOR)); + + mSpacingsPaint = new Paint(); + mSpacingsPaint.setColor(Color.parseColor(DEFAULT_SPACING_COLOR)); + + mBaselineGridCellSize = mDensity * DEFAULT_BASELINE_GRID_CELL_SIZE_DIP; + } + + /** + * Whether or not the baseline grid should be drawn. + */ + public boolean isBaselineGridVisible() { + return mBaselineGridVisible; + } + + /** + * Sets the baseline grid visibility. + */ + public DesignSpec setBaselineGridVisible(boolean visible) { + if (mBaselineGridVisible == visible) { + return this; + } + + mBaselineGridVisible = visible; + invalidateSelf(); + + return this; + } + + /** + * Sets the size of the baseline grid cells. By default, it uses the + * material design 8dp cell size. + */ + public DesignSpec setBaselineGridCellSize(float cellSize) { + if (mBaselineGridCellSize == cellSize) { + return this; + } + + mBaselineGridCellSize = cellSize; + invalidateSelf(); + + return this; + } + + /** + * Sets the baseline grid color. + */ + public DesignSpec setBaselineGridColor(int color) { + if (mBaselineGridPaint.getColor() == color) { + return this; + } + + mBaselineGridPaint.setColor(color); + invalidateSelf(); + + return this; + } + + /** + * Whether or not the keylines should be drawn. + */ + public boolean areKeylinesVisible() { + return mKeylinesVisible; + } + + /** + * Sets the visibility of keylines. + */ + public DesignSpec setKeylinesVisible(boolean visible) { + if (mKeylinesVisible == visible) { + return this; + } + + mKeylinesVisible = visible; + invalidateSelf(); + + return this; + } + + /** + * Sets the keyline color. + */ + public DesignSpec setKeylinesColor(int color) { + if (mKeylinesPaint.getColor() == color) { + return this; + } + + mKeylinesPaint.setColor(color); + invalidateSelf(); + + return this; + } + + /** + * Adds a keyline to the {@link DesignSpec}. + */ + public DesignSpec addKeyline(float position, From from) { + final Keyline keyline = new Keyline(position * mDensity, from); + if (mKeylines.contains(keyline)) { + return this; + } + + mKeylines.add(keyline); + return this; + } + + /** + * Whether or not the spacing markers should be drawn. + */ + public boolean areSpacingsVisible() { + return mSpacingsVisible; + } + + /** + * Sets the visibility of spacing markers. + */ + public DesignSpec setSpacingsVisible(boolean visible) { + if (mSpacingsVisible == visible) { + return this; + } + + mSpacingsVisible = visible; + invalidateSelf(); + + return this; + } + + /** + * Sets the spacing mark color. + */ + public DesignSpec setSpacingsColor(int color) { + if (mSpacingsPaint.getColor() == color) { + return this; + } + + mSpacingsPaint.setColor(color); + invalidateSelf(); + + return this; + } + + /** + * Adds a spacing mark to the {@link DesignSpec}. + */ + public DesignSpec addSpacing(float position, float size, From from) { + final Spacing spacing = new Spacing(position * mDensity, size * mDensity, from); + if (mSpacings.contains(spacing)) { + return this; + } + + mSpacings.add(spacing); + return this; + } + + private void drawBaselineGrid(Canvas canvas) { + if (!mBaselineGridVisible) { + return; + } + + final int width = getIntrinsicWidth(); + final int height = getIntrinsicHeight(); + + float x = mBaselineGridCellSize; + while (x < width) { + canvas.drawLine(x, 0, x, height, mBaselineGridPaint); + x += mBaselineGridCellSize; + } + + float y = mBaselineGridCellSize; + while (y < height) { + canvas.drawLine(0, y, width, y, mBaselineGridPaint); + y += mBaselineGridCellSize; + } + } + + private void drawKeylines(Canvas canvas) { + if (!mKeylinesVisible) { + return; + } + + final int width = getIntrinsicWidth(); + final int height = getIntrinsicHeight(); + + final int count = mKeylines.size(); + for (int i = 0; i < count; i++) { + final Keyline keyline = mKeylines.get(i); + + final float position; + switch (keyline.from) { + case LEFT: + case TOP: + position = keyline.position; + break; + + case RIGHT: + position = width - keyline.position; + break; + + case BOTTOM: + position = height - keyline.position; + break; + + case VERTICAL_CENTER: + position = (height / 2) + keyline.position; + break; + + case HORIZONTAL_CENTER: + position = (width / 2) + keyline.position; + break; + + default: + throw new IllegalStateException("Invalid keyline offset"); + } + + switch (keyline.from) { + case LEFT: + case RIGHT: + case HORIZONTAL_CENTER: + canvas.drawLine(position, 0, position, height, mKeylinesPaint); + break; + + case TOP: + case BOTTOM: + case VERTICAL_CENTER: + canvas.drawLine(0, position, width, position, mKeylinesPaint); + break; + } + } + } + + private void drawSpacings(Canvas canvas) { + if (!mSpacingsVisible) { + return; + } + + final int width = getIntrinsicWidth(); + final int height = getIntrinsicHeight(); + + final int count = mSpacings.size(); + for (int i = 0; i < count; i++) { + final Spacing spacing = mSpacings.get(i); + + final float position1; + final float position2; + switch (spacing.from) { + case LEFT: + case TOP: + position1 = spacing.offset; + position2 = position1 + spacing.size; + break; + + case RIGHT: + position1 = width - spacing.offset + spacing.size; + position2 = width - spacing.offset; + break; + + case BOTTOM: + position1 = height - spacing.offset + spacing.size; + position2 = height - spacing.offset; + break; + + case VERTICAL_CENTER: + position1 = (height / 2) + spacing.offset; + position2 = position1 + spacing.size; + break; + + case HORIZONTAL_CENTER: + position1 = (width / 2) + spacing.offset; + position2 = position1 + spacing.size; + break; + + default: + throw new IllegalStateException("Invalid spacing offset"); + } + + switch (spacing.from) { + case LEFT: + case RIGHT: + case HORIZONTAL_CENTER: + canvas.drawRect(position1, 0, position2, height, mSpacingsPaint); + break; + + case TOP: + case BOTTOM: + case VERTICAL_CENTER: + canvas.drawRect(0, position1, width, position2, mSpacingsPaint); + break; + } + } + } + + /** + * Draws the {@link DesignSpec}. You should call this in your {@link View}'s + * {@link View#onDraw(Canvas)} method if you're not simply enclosing it with a + * {@link DesignSpecFrameLayout}. + */ + @Override + public void draw(Canvas canvas) { + drawSpacings(canvas); + drawBaselineGrid(canvas); + drawKeylines(canvas); + } + + @Override + public int getIntrinsicWidth() { + return mHostView.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mHostView.getHeight(); + } + + @Override + public void setAlpha(int alpha) { + mBaselineGridPaint.setAlpha(alpha); + mKeylinesPaint.setAlpha(alpha); + mSpacingsPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mBaselineGridPaint.setColorFilter(cf); + mKeylinesPaint.setColorFilter(cf); + mSpacingsPaint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + /** + * Creates a new {@link DesignSpec} instance from a resource ID using a {@link View} + * that will provide the {@link DesignSpec}'s intrinsic dimensions. + * + * @param view The {@link View} who will own the new {@link DesignSpec} instance. + * @param resId The resource ID pointing to a raw JSON resource. + * @return The newly created {@link DesignSpec} instance. + */ + public static DesignSpec fromResource(View view, int resId) { + final Resources resources = view.getResources(); + final DesignSpec spec = new DesignSpec(resources, view); + if (resId == 0) { + return spec; + } + + final JSONObject json; + try { + json = RawResource.getAsJSON(resources, resId); + } catch (IOException e) { + throw new IllegalStateException("Could not read design spec resource", e); + } + + final float density = resources.getDisplayMetrics().density; + + spec.setBaselineGridCellSize(density * json.optInt(JSON_KEY_BASELINE_GRID_CELL_SIZE, + DEFAULT_BASELINE_GRID_CELL_SIZE_DIP)); + + spec.setBaselineGridVisible(json.optBoolean(JSON_KEY_BASELINE_GRID_VISIBLE, + DEFAULT_BASELINE_GRID_VISIBLE)); + spec.setKeylinesVisible(json.optBoolean(JSON_KEY_KEYLINES_VISIBLE, + DEFAULT_KEYLINES_VISIBLE)); + spec.setSpacingsVisible(json.optBoolean(JSON_KEY_SPACINGS_VISIBLE, + DEFAULT_SPACINGS_VISIBLE)); + + spec.setBaselineGridColor(Color.parseColor(json.optString(JSON_KEY_BASELINE_GRID_COLOR, + DEFAULT_BASELINE_GRID_COLOR))); + spec.setKeylinesColor(Color.parseColor(json.optString(JSON_KEY_KEYLINES_COLOR, + DEFAULT_KEYLINE_COLOR))); + spec.setSpacingsColor(Color.parseColor(json.optString(JSON_KEY_SPACINGS_COLOR, + DEFAULT_SPACING_COLOR))); + + final JSONArray keylines = json.optJSONArray(JSON_KEY_KEYLINES); + if (keylines != null) { + final int keylineCount = keylines.length(); + for (int i = 0; i < keylineCount; i++) { + try { + final JSONObject keyline = keylines.getJSONObject(i); + spec.addKeyline(keyline.getInt(JSON_KEY_OFFSET), + From.valueOf(keyline.getString(JSON_KEY_FROM).toUpperCase())); + } catch (JSONException e) { + continue; + } + } + } + + final JSONArray spacings = json.optJSONArray(JSON_KEY_SPACINGS); + if (spacings != null) { + final int spacingCount = spacings.length(); + for (int i = 0; i < spacingCount; i++) { + try { + final JSONObject spacing = spacings.getJSONObject(i); + spec.addSpacing(spacing.getInt(JSON_KEY_OFFSET), spacing.getInt(JSON_KEY_SIZE), + From.valueOf(spacing.getString(JSON_KEY_FROM).toUpperCase())); + } catch (JSONException e) { + continue; + } + } + } + + return spec; + } +} |