/**
******************************************************************************
* @file AttitudeView.java
* @author Tau Labs, http://taulabs.org, Copyright (C) 2012-2013
* @brief A view for UAV attitude.
* @see The GNU Public License (GPL) Version 3
*****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.openpilot_nonag.androidgcs;
import org.openpilot_nonag.androidgcs.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.view.View;
public class AttitudeView extends View {
private final float RADIUS = 0.8f;
RectF rollIndicatorLocation = new RectF();
private Paint markerPaint;
private Paint centerPaint;
private Paint thinLinePaint;
private Paint skyPaint;
private Paint groundPaint;
private Paint horizonPaint;
private Path triangle;
private int MAX_WIDTH = 2000;
private int MAX_HEIGHT = 2000;
private Paint pitchLabelPaint;
private final Rect pitchTextBounds = new Rect();
public AttitudeView(Context context) {
super(context);
initAttitudeView();
}
public AttitudeView(Context context, AttributeSet ats, int defaultStyle) {
super(context, ats, defaultStyle);
initAttitudeView();
}
public AttitudeView(Context context, AttributeSet ats) {
super(context, ats);
initAttitudeView();
}
protected void initAttitudeView() {
setFocusable(true);
markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
markerPaint.setStyle(Paint.Style.STROKE);
markerPaint.setStrokeWidth(3);
markerPaint.setColor(Color.WHITE);
centerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
centerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
centerPaint.setColor(Color.GREEN);
thinLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
thinLinePaint.setStyle(Paint.Style.FILL_AND_STROKE);
thinLinePaint.setStrokeWidth(2);
thinLinePaint.setColor(Color.WHITE);
pitchLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
pitchLabelPaint.setColor(Color.WHITE);
pitchLabelPaint.setTextSize(35);
pitchLabelPaint.getTextBounds("-20", 0, 3, pitchTextBounds);
skyPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
skyPaint.setColor(Color.BLUE);
skyPaint.setStyle(Paint.Style.FILL_AND_STROKE);
groundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
groundPaint.setColor(0xFF483843);
horizonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
horizonPaint.setColor(Color.WHITE);
horizonPaint.setStrokeWidth(3);
LinearGradient skyShader = new LinearGradient(0, -400, 0, 400, Color.WHITE, 0xFF6589E2, TileMode.CLAMP);
skyPaint.setShader(skyShader);
LinearGradient groundShader = new LinearGradient(0, 400, 0, 1000, 0xFFA56030, Color.BLACK, TileMode.CLAMP);
groundPaint.setShader(groundShader);
triangle = new Path();
}
/**
* @see android.view.View#measure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* Determines the height of this view
* @param measureSpec A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = MAX_HEIGHT;
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
/**
* Determines the width of this view
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = MAX_WIDTH;
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
private float roll;
public void setRoll(double roll) {
this.roll = (float) roll;
invalidate();
}
private float pitch;
public void setPitch(double d) {
this.pitch = (float) d;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
final int PX = getMeasuredWidth() / 2;
final int PY = getMeasuredHeight() / 2;
// Magic value calibrated for this image
final float DEG_TO_PX = (PY) / 50.0f; // Magic number for how to scale pitch
canvas.save();
canvas.rotate(-roll, PX, PY);
canvas.save();
canvas.translate(0, pitch * DEG_TO_PX);
// Draw the horizon and pitch indicator
canvas.drawRect(PX - PX * 2, PY, PX + PX * 2, PY + PY * 2, groundPaint);
canvas.drawRect(PX - PX * 2, PY - PY * 2, PX + PX * 2, PY, skyPaint);
canvas.drawLine(PX - PX * 2, PY, PX + PX * 2, PY, horizonPaint);
// Draw the pitch indicator
float [] pitchAngles = {-20, -10, 10, 20};
for (int i = 0; i < pitchAngles.length; i++) {
final float W = 100;
float angle = pitchAngles[i];
canvas.drawLine(PX - W, PY + DEG_TO_PX * angle, PX - 50, PY + DEG_TO_PX * angle, markerPaint);
canvas.drawLine(PX - W, PY + DEG_TO_PX * angle, PX - W, PY + DEG_TO_PX * angle - Math.copySign(20, angle), markerPaint);
canvas.drawLine(PX + 50, PY + DEG_TO_PX * angle, PX + W, PY + DEG_TO_PX * angle, markerPaint);
canvas.drawLine(PX + W, PY + DEG_TO_PX * angle, PX + W, PY + DEG_TO_PX * angle - Math.copySign(20, angle), markerPaint);
String lbl = Integer.toString((int) angle);
canvas.drawText(lbl,
PX - W - pitchLabelPaint.measureText(lbl) - 10,
PY + DEG_TO_PX * angle + pitchTextBounds.height() / 2 - Math.copySign(10, angle),
pitchLabelPaint);
}
canvas.restore();
// Draw the overlay that only rolls
float r = RADIUS * Math.min(PX, PY);
float r_s = r * 1.05f;
rollIndicatorLocation.set(PX - r, PY - r, PX + r, PY + r);
canvas.drawArc(rollIndicatorLocation, 210, 120, false, markerPaint);
float angles[] = {-60,-45,-30,-15,-15,0,15,30,45,60};
//float angles[] = {-45, 0, 45}; //{-60,-45,-30,-15,-15,0,15,30,45,60};
for (int i = 0; i < angles.length; i++) {
float angle = angles[i];
float dx = (float) Math.sin(angle * Math.PI / 180f); /// * 180f / Math.PI);
float dy = (float) Math.cos(angle * Math.PI / 180f) ; // * 180f / Math.PI);
canvas.drawLine(PX - dx * (r-1), PY - dy * (r-1), PX - dx * r_s, PY - dy * r_s, markerPaint);
}
triangle.reset();
triangle.moveTo(PX, PY - r - markerPaint.getStrokeWidth() / 2);
triangle.lineTo(PX + 15, PY - r - 25 - markerPaint.getStrokeWidth() / 2);
triangle.lineTo(PX - 15, PY - r - 25 - markerPaint.getStrokeWidth() / 2);
triangle.lineTo(PX, PY - r - markerPaint.getStrokeWidth() / 2);
canvas.drawPath(triangle,thinLinePaint);
canvas.restore();
// Draw the overlay that never moves
// Put marker in the center
canvas.drawLine(PX-40, PY, PX+40, PY, thinLinePaint);
canvas.drawLine(PX, PY, PX, PY-40, thinLinePaint);
canvas.drawCircle(PX, PY, 7, thinLinePaint);
canvas.drawCircle(PX, PY, 6, centerPaint);
// Indicate horizontal
canvas.drawLine(PX-220, PY, PX-100, PY, thinLinePaint);
canvas.drawLine(PX+220, PY, PX+100, PY, thinLinePaint);
// Indicate top of vertical
triangle.reset();
triangle.moveTo(PX, PY - r + centerPaint.getStrokeWidth() / 2);
triangle.lineTo(PX + 15, PY - r + 25 + centerPaint.getStrokeWidth() / 2);
triangle.lineTo(PX - 15, PY - r + 25 + centerPaint.getStrokeWidth() / 2);
triangle.lineTo(PX, PY - r);
canvas.drawPath(triangle,centerPaint);
}
}