blob: 1796381aa1efc25cd40eda98effe08805a66d556 [file] [log] [blame]
/*
* Copyright (C) 2013 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.editors.navigation;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.tools.idea.configurations.Configuration;
import com.android.tools.idea.rendering.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.ui.JBColor;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.List;
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class AndroidRootComponent extends JComponent {
public static final boolean DEBUG = false;
private final RenderingParameters myRenderingParameters;
private final PsiFile myLayoutFile;
private final boolean myIsMenu;
@NotNull Transform transform = createTransform(1);
private Image myScaledImage;
private RenderResult myRenderResult = null;
private boolean myRenderPending = false;
public AndroidRootComponent(@NotNull RenderingParameters renderingParameters, @Nullable PsiFile layoutFile, boolean isMenu) {
myRenderingParameters = renderingParameters;
myLayoutFile = layoutFile;
myIsMenu = isMenu;
}
@Nullable
private RenderResult getRenderResult() {
return myRenderResult;
}
private void setRenderResult(@Nullable RenderResult renderResult) {
Container parent = getParent();
if (parent == null) { // this is coming in of a different thread - we may have been detached form the view hierarchy in the meantime
return;
}
myRenderResult = renderResult;
if (myIsMenu) {
revalidate();
}
// once we have finished rendering we know where our internal views are and our parent needs to repaint (arrows etc.)
//repaint();
parent.repaint();
}
public float getScale() {
return transform.myScale;
}
private void invalidate2() {
myScaledImage = null;
}
public void setScale(float scale) {
transform = createTransform(scale);
invalidate2();
}
private Transform createTransform(float scale) {
if (myIsMenu) {
return new Transform(scale) {
private int getDx() {
RenderedView menu = getMenu(myRenderResult);
return (menu == null) ? 0 : menu.x;
}
@Override
public int modelToViewX(int d) {
return super.modelToViewX(d - getDx());
}
@Override
public int viewToModelX(int x) {
return super.viewToModelX(x) + getDx();
}
};
}
return new Transform(scale);
}
@Nullable
private static RenderedView getMenu(RenderResult renderResult) {
RenderedView root = getRoot(renderResult);
if (root == null) {
return null;
}
return root.getChildren().get(0);
}
private static com.android.navigation.Dimension size(@Nullable RenderedView view) {
if (view == null) {
//return com.android.navigation.Dimension.ZERO;
return new com.android.navigation.Dimension(100, 100); // width/height 0 and 1 is too small to cause an invalidate, for some reason
}
return new com.android.navigation.Dimension(view.w, view.h);
}
@Override
public Dimension getPreferredSize() {
return transform.modelToView(myIsMenu ? size(getMenu(myRenderResult)) : myRenderingParameters.getDeviceScreenSize());
}
//ScalableImage image = myRenderResult.getImage();
//if (image != null) {
// image.paint(g);
//}
/*
if (false) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC);
//g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
g2.drawImage(image, 0, 0, getWidth(), getHeight(), 0, 0, image.getWidth(), image.getHeight(), null);
}
if (false) {
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
}
*/
@Nullable
private Image getScaledImage() {
if (myScaledImage == null || myScaledImage.getWidth(null) != getWidth() || myScaledImage.getHeight(null) != getHeight()) {
ScalableImage scalableImage = (myRenderResult == null) ? null : myRenderResult.getImage();
BufferedImage image = (scalableImage == null) ? null : scalableImage.getOriginalImage();
myScaledImage = (image == null) ? null : ImageUtils.scale(image, transform.myScale, transform.myScale, 0, 0);
}
return myScaledImage;
}
private void center(Graphics g, String message, Font font, int height) {
int messageWidth = getFontMetrics(font).stringWidth(message);
g.drawString(message, (getWidth() - messageWidth) / 2, height);
}
@Override
public void paintComponent(Graphics g) {
Image scaledImage = getScaledImage();
if (scaledImage != null) {
if (myIsMenu) {
Point point = transform.modelToView(com.android.navigation.Point.ORIGIN);
g.drawImage(scaledImage, point.x, point.y, null);
}
else {
g.drawImage(scaledImage, 0, 0, null);
}
}
else {
g.setColor(JBColor.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(JBColor.GRAY);
Font font = g.getFont();
int vCenter = getHeight() / 2;
//center(g, "Initialising...", font, vCenter);
String message = "[" + (myLayoutFile == null ? "no xml resource" : myLayoutFile.getName()) + "]";
center(g, message, font, vCenter);
//center(g, message, font, vCenter + font.getSize() * 2);
render();
}
}
private void render() {
if (myLayoutFile == null) {
return;
}
Project project = myRenderingParameters.myProject;
final AndroidFacet facet = myRenderingParameters.myFacet;
final Configuration configuration = myRenderingParameters.myConfiguration;
if (project.isDisposed()) {
return;
}
if (myRenderPending) { // already rendering
return;
}
myRenderPending = true;
// The rendering service takes long enough to initialise that we don't want to do this from the EDT.
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
Module module = facet.getModule();
RenderLogger logger = new RenderLogger(myLayoutFile.getName(), module);
final RenderService service = RenderService.create(facet, module, myLayoutFile, configuration, logger, null);
if (service != null) {
RenderResult renderedResult = service.render();
if (renderedResult != null) {
RenderSession session = renderedResult.getSession();
if (session != null) {
Result result = session.getResult();
if (result.isSuccess()) {
setRenderResult(renderedResult);
service.dispose();
return;
}
}
}
if (DEBUG) System.out.println("AndroidRootComponent: rendering failed ");
}
}
});
}
@Nullable
public RenderedView getRootView() {
RenderResult renderResult = getRenderResult();
if (renderResult == null) {
return null;
}
return getRoot(renderResult);
}
@Nullable
private static RenderedView getRoot(@Nullable RenderResult renderResult) {
if (renderResult == null) {
return null;
}
RenderedViewHierarchy hierarchy = renderResult.getHierarchy();
if (hierarchy == null) {
return null;
}
List<RenderedView> roots = hierarchy.getRoots();
if (roots.isEmpty()) {
return null;
}
return roots.get(0);
}
@Nullable
public RenderedView getRenderedView(Point p) {
RenderResult renderResult = getRenderResult();
if (renderResult == null) {
return null;
}
RenderedViewHierarchy hierarchy = renderResult.getHierarchy();
if (hierarchy == null) {
return null;
}
return hierarchy.findLeafAt(transform.viewToModelX(p.x), transform.viewToModelY(p.y));
}
}