blob: 3ae0b3a2da943a5d0c8a5638a1a77a8059a167ae [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 com.android.tools.idea.avdmanager;
import com.android.ide.common.rendering.HardwareConfigHelper;
import com.android.resources.*;
import com.android.sdklib.devices.Device;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.util.ui.GraphicsUtil;
import icons.AndroidIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
import java.text.DecimalFormat;
import java.util.List;
import static com.android.tools.idea.avdmanager.AvdWizardConstants.*;
/**
* A preview component for displaying information about
* a device definition. This panel displays the dimensions of the device
* (both physical and in pixels) and some information about the screen
* size and shape.
*/
public class DeviceDefinitionPreview extends JPanel implements DeviceDefinitionList.DeviceCategorySelectionListener {
private static final String NO_DEVICE_SELECTED = "No Device Selected";
private static final int FIGURE_PADDING = 3;
private static final DecimalFormat FORMAT = new DecimalFormat(".##\"");
public static final int DIMENSION_LINE_WIDTH = 1; // px
public static final int OUTLINE_LINE_WIDTH = 5; // px
private Device myDevice;
double myMaxOutlineHeight;
double myMaxOutlineWidth;
double myMinOutlineHeightIn;
double myMinOutlineWidthIn;
private static final int PADDING = 20;
private static final JBColor OUR_GRAY = new JBColor(Gray._192, Gray._96);
public void setDevice(@Nullable Device device) {
myDevice = device;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
GraphicsUtil.setupAntialiasing(g);
GraphicsUtil.setupAAPainting(g);
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(JBColor.background());
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setColor(JBColor.foreground());
g2d.setFont(STANDARD_FONT);
if (myDevice == null) {
FontMetrics metrics = g2d.getFontMetrics();
g2d.drawString(NO_DEVICE_SELECTED,
(getWidth() - metrics.stringWidth(NO_DEVICE_SELECTED)) / 2,
(getHeight() - metrics.getHeight()) / 2 );
return;
}
boolean isCircular = myDevice.isScreenRound();
// Paint our icon
Icon icon = getIcon(myDevice);
icon.paintIcon(this, g, PADDING/2, PADDING/2);
// Paint the device name
g2d.setFont(TITLE_FONT);
FontMetrics metrics = g.getFontMetrics(TITLE_FONT);
g2d.drawString(myDevice.getDisplayName(), 50, PADDING + metrics.getHeight() / 2);
g2d.drawLine(0, 50, getWidth(), 50);
// Paint the device outline with dimensions labelled
Dimension screenSize = getScaledDimension(myDevice);
Dimension pixelScreenSize = myDevice.getScreenSize(myDevice.getDefaultState().getOrientation());
if (screenSize != null) {
if (screenSize.getHeight() <= 0) {
screenSize.height = 1;
}
if (screenSize.getWidth() <= 0) {
screenSize.width = 1;
}
RoundRectangle2D roundRect = new RoundRectangle2D.Double(PADDING, 100, screenSize.width, screenSize.height, 10, 10);
g2d.setStroke(new BasicStroke(DIMENSION_LINE_WIDTH));
g2d.setColor(OUR_GRAY);
g2d.setFont(FIGURE_FONT);
metrics = g2d.getFontMetrics(FIGURE_FONT);
int stringHeight = metrics.getHeight() - metrics.getDescent();
// Paint the width dimension
String widthString = Integer.toString(pixelScreenSize.width) + "px";
int widthLineY = 95 - (metrics.getHeight() - metrics.getDescent()) / 2;
g2d.drawLine(PADDING, widthLineY, round(PADDING + screenSize.width), widthLineY);
// Erase the part of the line that the text overlays
g2d.setColor(JBColor.background());
int widthStringWidth = metrics.stringWidth(widthString);
int widthTextX = round(PADDING + (screenSize.width - widthStringWidth) / 2);
g2d.drawLine(widthTextX - FIGURE_PADDING, widthLineY, widthTextX + widthStringWidth + FIGURE_PADDING, widthLineY);
// Paint the width text
g2d.setColor(JBColor.foreground());
g2d.drawString(widthString, widthTextX, 95);
// Paint the height dimension
g2d.setColor(OUR_GRAY);
String heightString = Integer.toString(pixelScreenSize.height) + "px";
int heightLineX = round(PADDING + screenSize.width + 15);
g2d.drawLine(heightLineX, 100, heightLineX, round(100 + screenSize.height));
// Erase the part of the line that the text overlays
g2d.setColor(JBColor.background());
int heightTextY = round(100 + (screenSize.height + stringHeight) / 2);
g2d.drawLine(heightLineX, heightTextY + FIGURE_PADDING, heightLineX, heightTextY - stringHeight - FIGURE_PADDING);
// Paint the height text
g2d.setColor(JBColor.foreground());
g2d.drawString(heightString, heightLineX - 10, heightTextY);
// Paint the diagonal dimension
g2d.setColor(OUR_GRAY);
String diagString = FORMAT.format(myDevice.getDefaultHardware().getScreen().getDiagonalLength());
int diagTextX = round(PADDING + (screenSize.width - metrics.stringWidth(diagString)) / 2);
int diagTextY = round(100 + (screenSize.height + stringHeight) / 2);
double chin = (double)myDevice.getChinSize();
chin *= screenSize.getWidth() / myDevice.getScreenSize(myDevice.getDefaultState().getOrientation()).getWidth();
Line2D diagLine = new Line2D.Double(PADDING, 100 + screenSize.height + chin, PADDING + screenSize.width, 100);
if (isCircular) {
// Move the endpoints of the line to within the circle. Each endpoint must move towards the center axis of the circle by
// 0.5 * (l - l/sqrt(2)) where l is the diameter of the circle.
double dist = 0.5 * (screenSize.width - screenSize.width / Math.sqrt(2));
diagLine.setLine(diagLine.getX1() + dist, diagLine.getY1() - dist, diagLine.getX2() - dist, diagLine.getY2() + dist);
}
g2d.draw(diagLine);
// Erase the part of the line that the text overlays
g2d.setColor(JBColor.background());
Rectangle erasureRect = new Rectangle(diagTextX - FIGURE_PADDING, diagTextY - stringHeight - FIGURE_PADDING,
metrics.stringWidth(diagString) + FIGURE_PADDING * 2, stringHeight + FIGURE_PADDING * 2);
g2d.fill(erasureRect);
// Paint the diagonal text
g2d.setColor(JBColor.foreground());
g2d.drawString(diagString, diagTextX, diagTextY);
// Finally, paint the outline
g2d.setStroke(new BasicStroke(OUTLINE_LINE_WIDTH));
g2d.setColor(JBColor.foreground());
if (isCircular) {
double x = roundRect.getX();
double y = roundRect.getY();
Ellipse2D circle = new Ellipse2D.Double(x, y, screenSize.width, screenSize.height + chin);
g2d.draw(circle);
if (chin > 0) {
erasureRect = new Rectangle((int)x,
(int)(y + screenSize.height + OUTLINE_LINE_WIDTH / 2 + 1),
screenSize.width, (int)chin + OUTLINE_LINE_WIDTH / 2 + 1);
g2d.setColor(JBColor.background());
g2d.fill(erasureRect);
g2d.setColor(JBColor.foreground());
double halfChinWidth = Math.sqrt(chin * (screenSize.width - chin)) - OUTLINE_LINE_WIDTH / 2;
int chinX = (int)(x + screenSize.width / 2 - halfChinWidth);
g2d.drawLine(chinX, (int)(y + screenSize.height),
(int)(chinX + halfChinWidth * 2), (int)(y + screenSize.height));
}
} else {
g2d.draw(roundRect);
}
// Paint the details. If it's a portrait phone, then paint to the right of the rect.
// If it's a landscape tablet/tv, paint below.
g2d.setFont(STANDARD_FONT);
metrics = g2d.getFontMetrics(STANDARD_FONT);
stringHeight = metrics.getHeight();
int infoSegmentX;
int infoSegmentY;
if (myDevice.getDefaultState().getOrientation().equals(ScreenOrientation.PORTRAIT)) {
infoSegmentX = round(PADDING + screenSize.width + metrics.stringWidth(heightString) + PADDING);
infoSegmentY = 100;
} else {
infoSegmentX = PADDING;
infoSegmentY = round(100 + screenSize.height + PADDING);
}
infoSegmentY += stringHeight;
ScreenSize size = myDevice.getDefaultHardware().getScreen().getSize();
if (size != null) {
g2d.drawString("Size: " + size.getResourceValue(), infoSegmentX, infoSegmentY);
infoSegmentY += stringHeight;
}
ScreenRatio ratio = myDevice.getDefaultHardware().getScreen().getRatio();
if (ratio != null) {
g2d.drawString("Ratio: " + ratio.getResourceValue(), infoSegmentX, infoSegmentY);
infoSegmentY += stringHeight;
}
Density pixelDensity = myDevice.getDefaultHardware().getScreen().getPixelDensity();
if (pixelDensity != null) {
g2d.drawString("Density: " + pixelDensity.getResourceValue(), infoSegmentX, infoSegmentY);
}
}
}
private static int round(double d) {
return (int)Math.round(d);
}
/**
* @return A scaled dimension of the given device's screen that will fit within this component's bounds.
*/
@Nullable
private Dimension getScaledDimension(@NotNull Device device) {
Dimension pixelSize = device.getScreenSize(device.getDefaultState().getOrientation());
if (pixelSize == null) {
return null;
}
double diagonalIn = device.getDefaultHardware().getScreen().getDiagonalLength();
double sideRatio = pixelSize.getWidth() / pixelSize.getHeight();
double heightIn = diagonalIn / Math.sqrt(1 + sideRatio);
double widthIn = sideRatio * heightIn;
double maxHeightIn = myMaxOutlineHeight == 0 ? heightIn : myMaxOutlineHeight;
double maxWidthIn = myMaxOutlineWidth == 0 ? widthIn : myMaxOutlineWidth;
double maxDimIn = Math.max(maxHeightIn, maxWidthIn);
double desiredMaxWidthPx = getWidth() / 2;
double desiredMaxHeightPx = getHeight() / 2;
double desiredMaxPx = Math.min(desiredMaxHeightPx, desiredMaxWidthPx);
double scalingFactorPxToIn = maxDimIn/desiredMaxPx;
// Test if we have to scale the min as well
double desiredMinWidthPx = getWidth() / 10;
double desiredMinHeightPx = getHeight() / 10;
double desiredMinIn = Math.max(desiredMinWidthPx, desiredMinHeightPx) * scalingFactorPxToIn;
double minDimIn = Math.min(myMinOutlineHeightIn, myMinOutlineWidthIn);
if (minDimIn < desiredMinIn) {
// compute F and C such that F * minDimIn + C = desiredMinIn and F * maxDimIn + C = maxDimIn
double f = (maxDimIn - desiredMinIn) / (maxDimIn - minDimIn);
double c = (desiredMinIn * myMaxOutlineWidth - maxDimIn * minDimIn) / (maxDimIn - minDimIn);
// scale the diagonal and then recompute the edges, since the edges need to be scaled evenly
diagonalIn = device.getDefaultHardware().getScreen().getDiagonalLength();
diagonalIn = diagonalIn * f + c;
heightIn = diagonalIn / Math.sqrt(1 + sideRatio);
widthIn = sideRatio * heightIn;
}
return new Dimension((int)(widthIn / scalingFactorPxToIn), (int)(heightIn / scalingFactorPxToIn));
}
/**
* @return an icon representing the given device's form factor. Defaults to Mobile if the form factor
* can not be detected.
*/
@NotNull
public static Icon getIcon(@Nullable Device device) {
if (device == null) {
return AndroidIcons.FormFactors.Mobile_32;
}
if (HardwareConfigHelper.isTv(device)) {
return AndroidIcons.FormFactors.Tv_32;
} else if (HardwareConfigHelper.isWear(device)) {
return AndroidIcons.FormFactors.Wear_32;
} else {
return AndroidIcons.FormFactors.Mobile_32;
}
}
@Override
public void onCategorySelectionChanged(@Nullable String category, @Nullable List<Device> devices) {
if (devices == null) {
myMaxOutlineHeight = 0;
myMaxOutlineWidth = 0;
myMinOutlineHeightIn = 0;
myMinOutlineWidthIn = 0;
} else {
double maxHeight = 0;
double maxWidth = 0;
double minHeight = Double.MAX_VALUE;
double minWidth = Double.MAX_VALUE;
for (Device d : devices) {
Dimension pixelSize = d.getScreenSize(d.getDefaultState().getOrientation());
if (pixelSize == null) {
continue;
}
double diagonal = d.getDefaultHardware().getScreen().getDiagonalLength();
double sideRatio = pixelSize.getWidth() / pixelSize.getHeight();
double heightIn = diagonal / Math.sqrt(1 + sideRatio * sideRatio);
double widthIn = sideRatio * heightIn;
maxWidth = Math.max(maxWidth, widthIn);
maxHeight = Math.max(maxHeight, heightIn);
minWidth = Math.min(minWidth, widthIn);
minHeight = Math.min(minHeight, heightIn);
}
myMaxOutlineHeight = maxHeight;
myMaxOutlineWidth = maxWidth;
myMinOutlineHeightIn = minHeight;
myMinOutlineWidthIn = minWidth;
}
}
}