blob: f0857bfa3a953fe7ca899db502cf52876610eb36 [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.rendering;
import com.android.ide.common.rendering.HardwareConfigHelper;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.rendering.api.ViewType;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.Screen;
import com.android.tools.idea.configurations.Configuration;
import com.android.tools.idea.configurations.RenderContext;
import com.android.tools.idea.rendering.multi.RenderPreviewManager;
import com.android.tools.idea.rendering.multi.RenderPreviewMode;
import com.google.common.base.Objects;
import com.intellij.android.designer.AndroidDesignerEditorProvider;
import com.intellij.android.designer.designSurface.graphics.DesignerGraphics;
import com.intellij.android.designer.designSurface.graphics.DrawingStyle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLayeredPane;
import com.intellij.util.ui.AsyncProcessIcon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import static com.android.SdkConstants.TAG_ITEM;
import static com.android.tools.idea.rendering.RenderErrorPanel.SIZE_ERROR_PANEL_DYNAMICALLY;
/** A panel displaying a layoutlib render result as well as errors */
public class RenderedPanel extends JPanel implements Disposable {
private static final Integer LAYER_PROGRESS = JLayeredPane.POPUP_LAYER + 100;
private static final boolean DEBUG_SHOW_VIEWS = false;
protected RenderResult myRenderResult;
protected RenderPreviewManager myPreviewManager;
protected List<RenderedView> mySelectedViews;
protected RenderContext myContext;
private boolean myZoomToFit = true;
private final List<ProgressIndicator> myProgressIndicators = new ArrayList<ProgressIndicator>();
private final JComponent myImagePanel = new JComponent() {
};
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
private MyProgressPanel myProgressPanel;
private RenderErrorPanel myErrorPanel;
private int myErrorPanelHeight = -1;
public RenderedPanel(boolean installSelectionListeners) {
super(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, true));
updateBackgroundColor();
myImagePanel.setBackground(null);
MyImagePanelWrapper previewPanel = new MyImagePanelWrapper();
if (installSelectionListeners) {
previewPanel.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent mouseEvent) {
if (myRenderResult == null) {
return;
}
if (myRenderResult.getImage() != null) {
// Convert to model coordinates
int x1 = mouseEvent.getX();
int y1 = mouseEvent.getY();
x1 -= myImagePanel.getX();
y1 -= myImagePanel.getY();
Point p = fromScreenToModel(x1, y1);
if (p == null) {
return;
}
selectViewAt(p.x, p.y);
}
}
@Override
public void mouseClicked(MouseEvent mouseEvent) {
if (myRenderResult == null) {
return;
}
if (mouseEvent.getClickCount() == 2) {
// Double click: open in the UI editor
switchToLayoutEditor();
}
}
});
}
add(previewPanel);
myErrorPanel = new RenderErrorPanel();
myErrorPanel.setVisible(false);
previewPanel.add(myErrorPanel, JLayeredPane.POPUP_LAYER);
myProgressPanel = new MyProgressPanel();
previewPanel.add(myProgressPanel, LAYER_PROGRESS);
}
public void setRenderContext(@Nullable RenderContext context) {
myContext = context;
}
public Component getPaintComponent() {
return myImagePanel;
}
private void switchToLayoutEditor() {
if (myRenderResult != null) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
VirtualFile file = myRenderResult.getFile().getVirtualFile();
if (file != null) {
Project project = myRenderResult.getModule().getProject();
FileEditorManager.getInstance(project).setSelectedEditor(file, AndroidDesignerEditorProvider.ANDROID_DESIGNER_ID);
}
}
}, ModalityState.NON_MODAL);
}
}
protected void selectViewAt(int x, int y) {
RenderedView leaf = findLeaf(x, y, false);
if (handleMenu(leaf)) {
return;
}
while (leaf != null && leaf.tag == null) {
leaf = leaf.getParent();
}
selectView(leaf);
}
private boolean handleMenu(@Nullable RenderedView leaf) {
boolean showMenu = false;
if (leaf != null) {
ViewInfo view = leaf.view;
if (view != null) {
ViewType viewType = view.getViewType();
if (viewType != ViewType.USER) {
XmlFile xmlFile = myContext.getXmlFile();
if (ResourceHelper.getFolderType(xmlFile) == ResourceFolderType.MENU) {
// When rendering a menu file, don't hide menu when clicking outside of it
showMenu = true;
}
if (viewType == ViewType.ACTION_BAR_OVERFLOW) {
showMenu = !ActionBarHandler.isShowingMenu(myContext);
} else if (ActionBarHandler.isShowingMenu(myContext)) {
RenderedView v = leaf.getParent();
while (v != null) {
if (v.tag != null) {
// A view *containing* a system view is the menu
showMenu = true;
if (TAG_ITEM.equals(v.tag.getName())) {
PsiFile file = v.tag.getContainingFile();
if (file != null && file != xmlFile) {
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile != null) {
Project project = file.getProject();
int offset = v.tag.getTextOffset();
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, virtualFile, offset);
FileEditorManager.getInstance(project).openEditor(descriptor, true);
return true;
}
}
}
break;
}
v = v.getParent();
}
}
}
}
}
ActionBarHandler.showMenu(showMenu, myContext, true);
return false;
}
@Nullable
protected RenderedView findLeaf(int x, int y, boolean requireTag) {
RenderedViewHierarchy hierarchy = myRenderResult.getHierarchy();
assert hierarchy != null; // because image != null
RenderedView leaf = hierarchy.findLeafAt(x, y);
// If you've clicked on for example a list item, the view you clicked
// on may not correspond to a tag, it could be a designtime preview item,
// so search upwards for the nearest surrounding tag
if (requireTag) {
while (leaf != null && leaf.tag == null) {
leaf = leaf.getParent();
}
}
return leaf;
}
protected void selectView(@Nullable RenderedView leaf) {
}
/**
* Computes the corresponding layoutlib point from a screen point (relative to the top left corner of the rendered image)
*/
@Nullable
public Point fromScreenToModel(int x, int y) {
if (myRenderResult == null) {
return null;
}
RenderedImage image = myRenderResult.getImage();
if (image != null) {
double zoomFactor = image.getScale();
Rectangle imageBounds = image.getImageBounds();
if (imageBounds != null) {
x -= imageBounds.x;
y -= imageBounds.y;
double deviceFrameFactor = imageBounds.getWidth() / (double) image.getScaledWidth();
zoomFactor *= deviceFrameFactor;
}
x /= zoomFactor;
y /= zoomFactor;
return new Point(x, y);
}
return null;
}
/**
* Computes the corresponding layoutlib point from a screen point (relative to the top left corner of the rendered image)
*/
@Nullable
public Rectangle fromScreenToModel(int x, int y, int width, int height) {
if (myRenderResult == null) {
return null;
}
RenderedImage image = myRenderResult.getImage();
if (image != null) {
double zoomFactor = image.getScale();
Rectangle imageBounds = image.getImageBounds();
if (imageBounds != null) {
x -= imageBounds.x;
y -= imageBounds.y;
double deviceFrameFactor = imageBounds.getWidth() / (double) image.getScaledWidth();
zoomFactor *= deviceFrameFactor;
}
x /= zoomFactor;
y /= zoomFactor;
width /= zoomFactor;
height /= zoomFactor;
return new Rectangle(x, y, width, height);
}
return null;
}
/**
* Computes the corresponding screen coordinates (relative to the top left corner of the rendered image)
* for a layoutlib rectangle.
*/
@Nullable
public Rectangle fromModelToScreen(int x, int y, int width, int height) {
if (myRenderResult == null) {
return null;
}
RenderedImage image = myRenderResult.getImage();
if (image != null) {
double zoomFactor = image.getScale();
Rectangle imageBounds = image.getImageBounds();
if (imageBounds != null) {
double deviceFrameFactor = imageBounds.getWidth() / (double) image.getScaledWidth();
zoomFactor *= deviceFrameFactor;
}
x *= zoomFactor;
y *= zoomFactor;
width *= zoomFactor;
height *= zoomFactor;
if (imageBounds != null) {
x += imageBounds.x;
y += imageBounds.y;
}
return new Rectangle(x, y, width, height);
}
return null;
}
protected boolean paintRenderedImage(Component component, Graphics g, int px, int py) {
if (myRenderResult == null) {
return false;
}
RenderedImage image = myRenderResult.getImage();
if (image != null) {
image.paint(g, px, py);
// Paint hierarchy
if (DEBUG_SHOW_VIEWS) {
paintViews(g, px, py);
}
List<RenderedView> selectedViews = mySelectedViews;
if (selectedViews != null && !selectedViews.isEmpty() && !myErrorPanel.isVisible()) {
Shape prevClip = g.getClip();
Shape clip = null;
Configuration configuration = myContext.getConfiguration();
if (configuration != null) {
Device device = configuration.getDevice();
if (device != null && device.isScreenRound()) {
Screen screen = device.getDefaultHardware().getScreen();
int width = screen.getXDimension();
int height = screen.getYDimension();
Rectangle m = fromModelToScreen(0, 0, width, height);
if (m != null) {
clip = RenderedImage.getClip(device, m.x + px, m.y + py, m.width, m.height);
if (clip != null) {
g.setClip(clip);
}
}
}
}
for (RenderedView selected : selectedViews) {
Rectangle r = fromModelToScreen(selected.x, selected.y, selected.w, selected.h);
if (r == null) {
continue;
}
int x = r.x + px;
int y = r.y + py;
int w = r.width;
int h = r.height;
DesignerGraphics.drawFilledRect(DrawingStyle.SELECTION, g, x, y, w, h);
}
if (clip != null) {
g.setClip(prevClip);
}
}
return true;
}
return false;
}
private void paintViews(Graphics g, int px, int py) {
if (DEBUG_SHOW_VIEWS) {
Graphics2D g2d = (Graphics2D)g;
Composite prev = g2d.getComposite();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f));
RenderedViewHierarchy hierarchy = myRenderResult.getHierarchy();
if (hierarchy != null) {
for (RenderedView view : hierarchy.getRoots()) {
paintView(g, px, py, view);
}
}
g2d.setComposite(prev);
}
}
private void paintView(Graphics g, int px, int py, RenderedView view) {
if (DEBUG_SHOW_VIEWS) {
if (view.view == null || view.view.getViewType() == ViewType.USER) {
return;
}
Rectangle bounds = view.getBounds();
Rectangle r = fromModelToScreen(bounds.x, bounds.y, bounds.width, bounds.height);
if (r == null) {
return;
}
int x = r.x + px;
int y = r.y + py;
int w = Math.max(0, r.width - 1);
int h = Math.max(0, r.height - 1);
if (view.h <= 300) {
//noinspection UseJBColor
g.setColor(Color.RED);
g.fillRect(x, y, w, h);
}
//noinspection UseJBColor
g.setColor(Color.WHITE);
g.drawRect(x, y, w, h);
String className = view.view.getClassName();
if (className != null) {
className = className.substring(className.lastIndexOf('.') + 1);
Shape clip = g.getClip();
g.setClip(x, y, w, h);
g.drawString(className, x, y + h);
g.setClip(clip);
}
for (RenderedView child : view.getChildren()) {
paintView(g, px, py, child);
}
}
}
public void setRenderResult(@NotNull final RenderResult renderResult) {
double prevScale = myRenderResult != null && myRenderResult.getImage() != null ? myRenderResult.getImage().getScale() : 1;
myRenderResult = renderResult;
RenderedImage image = myRenderResult.getImage();
if (image != null) {
image.setDeviceFrameEnabled(myShowDeviceFrames && myRenderResult.getRenderTask() != null &&
myRenderResult.getRenderTask().getRenderingMode() == SessionParams.RenderingMode.NORMAL &&
myRenderResult.getRenderTask().getShowDecorations());
if (myPreviewManager != null && RenderPreviewMode.getCurrent() != RenderPreviewMode.NONE) {
Dimension fixedRenderSize = myPreviewManager.getFixedRenderSize();
if (fixedRenderSize != null) {
image.setMaxSize(fixedRenderSize.width, fixedRenderSize.height);
image.setUseLargeShadows(false);
}
}
image.setScale(prevScale);
}
mySelectedViews = null;
RenderLogger logger = myRenderResult.getLogger();
if (logger.hasProblems()) {
if (!myErrorPanel.isVisible()) {
myErrorPanelHeight = -1;
}
myErrorPanel.showErrors(myRenderResult);
myErrorPanel.setVisible(true);
} else {
myErrorPanel.setVisible(false);
}
repaint();
// Ensure that if we have a a preview mode enabled, it's shown
if (myPreviewManager != null && myPreviewManager.hasPreviews()) {
myPreviewManager.renderPreviews();
}
}
public RenderResult getRenderResult() {
return myRenderResult;
}
public void setSelectedViews(@Nullable List<RenderedView> views) {
if (!Objects.equal(views, mySelectedViews)) {
mySelectedViews = views;
repaint();
}
}
public synchronized void registerIndicator(@NotNull ProgressIndicator indicator) {
synchronized (myProgressIndicators) {
myProgressIndicators.add(indicator);
myProgressPanel.showProgressIcon();
}
}
public void unregisterIndicator(@NotNull ProgressIndicator indicator) {
synchronized (myProgressIndicators) {
myProgressIndicators.remove(indicator);
if (myProgressIndicators.size() == 0) {
myProgressPanel.hideProgressIcon();
}
}
}
protected void doRevalidate() {
revalidate();
updateImageSize();
repaint();
}
public void update() {
revalidate();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
doRevalidate();
}
});
}
public void updateImageSize() {
if (myRenderResult == null) {
return;
}
updateBackgroundColor();
RenderedImage image = myRenderResult.getImage();
if (image == null) {
myImagePanel.setSize(0, 0);
}
else {
if (myZoomToFit) {
double availableHeight = getPanelHeight();
double availableWidth = getPanelWidth();
final int MIN_SIZE = 200;
if (myPreviewManager != null && availableWidth > MIN_SIZE) {
int previewWidth = myPreviewManager.computePreviewWidth();
availableWidth = Math.max(MIN_SIZE, availableWidth - previewWidth);
}
image.zoomToFit((int)availableWidth, (int)availableHeight, false, 0, 0);
}
myImagePanel.setSize(getScaledImageSize());
repaint();
}
}
private void updateBackgroundColor() {
// Ensure the background color is right: light/dark when showing device chrome, gray when not
boolean useGray = false;
if (!myShowDeviceFrames) {
useGray = true;
} else if (myPreviewManager != null && RenderPreviewMode.getCurrent() != RenderPreviewMode.NONE) {
// TODO: Don't do this if showing device frames or if we're not in Darcula!
useGray = !RenderPreviewMode.getCurrent().showsDeviceFrames();
} else {
if (myRenderResult != null) {
RenderedImage image = myRenderResult.getImage();
if (image != null) {
Boolean framed = image.isFramed();
if (framed == null) {
return;
}
useGray = !framed;
}
}
}
Color background = useGray ? DrawingStyle.DESIGNER_BACKGROUND_COLOR : JBColor.WHITE;
if (getBackground() != background) {
setBackground(background);
}
}
protected double getPanelHeight() {
return getSize().getHeight() - 5;
}
protected double getPanelWidth() {
return getSize().getWidth() - 5;
}
public void zoomOut() {
myZoomToFit = false;
if (myRenderResult.getImage() != null) {
myRenderResult.getImage().zoomOut();
}
doRevalidate();
}
public void zoomIn() {
myZoomToFit = false;
if (myRenderResult.getImage() != null) {
myRenderResult.getImage().zoomIn();
}
doRevalidate();
}
public void zoomActual() {
myZoomToFit = false;
if (myRenderResult != null && myRenderResult.getImage() != null) {
myRenderResult.getImage().zoomActual();
}
doRevalidate();
}
public void setZoomToFit(boolean zoomToFit) {
myZoomToFit = zoomToFit;
doRevalidate();
}
public boolean isZoomToFit() {
return myZoomToFit;
}
@Override
public void dispose() {
if (myPreviewManager != null) {
myPreviewManager.dispose();
myPreviewManager = null;
}
myErrorPanel.dispose();
myErrorPanel = null;
}
// RenderContext helpers
@Nullable
public Module getModule() {
return myRenderResult != null ? myRenderResult.getModule() : null;
}
@Nullable
public XmlFile getXmlFile() {
return myRenderResult != null ? (XmlFile)myRenderResult.getFile() : null;
}
@Nullable
public VirtualFile getVirtualFile() {
return myRenderResult != null ? myRenderResult.getFile().getVirtualFile() : null;
}
public boolean hasAlphaChannel() {
return myRenderResult.getImage() != null && !myRenderResult.getImage().hasAlphaChannel();
}
@NotNull
public Component getComponent() {
return this;
}
@NotNull
public Dimension getFullImageSize() {
if (myRenderResult != null) {
RenderedImage scaledImage = myRenderResult.getImage();
if (scaledImage != null) {
return new Dimension(scaledImage.getOriginalWidth(), scaledImage.getOriginalHeight());
}
}
return RenderContext.NO_SIZE;
}
@NotNull
public Dimension getScaledImageSize() {
if (myRenderResult != null) {
RenderedImage scaledImage = myRenderResult.getImage();
if (scaledImage != null) {
return new Dimension(scaledImage.getScaledWidth(), scaledImage.getScaledHeight());
}
}
return RenderContext.NO_SIZE;
}
public Component getRenderComponent() {
return myImagePanel.getParent();
}
public void setPreviewManager(@Nullable RenderPreviewManager manager) {
if (manager == myPreviewManager) {
return;
}
Component renderComponent = getRenderComponent();
if (myPreviewManager != null) {
myPreviewManager.unregisterMouseListener(renderComponent);
myPreviewManager.dispose();
}
myPreviewManager = manager;
if (myPreviewManager != null) {
myPreviewManager.registerMouseListener(renderComponent);
}
}
@Nullable
public RenderPreviewManager getPreviewManager(@Nullable RenderContext context, boolean createIfNecessary) {
if (myPreviewManager == null && createIfNecessary && context != null) {
setPreviewManager(new RenderPreviewManager(context));
}
return myPreviewManager;
}
public void setMaxSize(int width, int height) {
RenderedImage scaledImage = myRenderResult.getImage();
if (scaledImage != null) {
scaledImage.setMaxSize(width, height);
scaledImage.setUseLargeShadows(width <= 0);
myImagePanel.revalidate();
}
}
private boolean myShowDeviceFrames = true;
public void setDeviceFramesEnabled(boolean on) {
myShowDeviceFrames = on;
if (myRenderResult != null) {
RenderedImage image = myRenderResult.getImage();
if (image != null) {
image.setDeviceFrameEnabled(on);
}
}
}
/**
* Layered pane which shows the rendered image, as well as (if applicable) an error message panel on top of the rendering
* near the bottom
*/
private class MyImagePanelWrapper extends JBLayeredPane {
public MyImagePanelWrapper() {
add(myImagePanel);
setBackground(null);
setOpaque(true);
}
@Override
public void doLayout() {
super.doLayout();
positionErrorPanel();
myProgressPanel.setBounds(0, 0, getWidth(), getHeight());
if (myPreviewManager == null || !myPreviewManager.hasPreviews()) {
centerComponents();
} else {
if (myRenderResult != null) {
RenderedImage image = myRenderResult.getImage();
if (image != null) {
int fixedWidth = image.getMaxWidth();
int fixedHeight = image.getMaxHeight();
if (fixedWidth > 0) {
myImagePanel.setLocation(Math.max(0, (fixedWidth - image.getScaledWidth()) / 2),
2 + Math.max(0, (fixedHeight - image.getScaledHeight()) / 2));
return;
}
}
}
myImagePanel.setLocation(0, 0);
}
}
private void centerComponents() {
Rectangle bounds = getBounds();
Point point = myImagePanel.getLocation();
point.x = (bounds.width - myImagePanel.getWidth()) / 2;
point.y = (bounds.height - myImagePanel.getHeight()) / 2;
// If we're squeezing the image to fit, and there's a drop shadow showing
// shift *some* space away from the tail portion of the drop shadow over to
// the left to make the image look more balanced
if (myRenderResult != null) {
if (point.x <= 2) {
RenderedImage image = myRenderResult.getImage();
// If there's a drop shadow
if (image != null) {
if (image.hasDropShadow()) {
point.x += ShadowPainter.SHADOW_SIZE / 3;
}
}
}
if (point.y <= 2) {
RenderedImage image = myRenderResult.getImage();
// If there's a drop shadow
if (image != null) {
if (image.hasDropShadow()) {
point.y += ShadowPainter.SHADOW_SIZE / 3;
}
}
}
}
myImagePanel.setLocation(point);
}
private void positionErrorPanel() {
int height = getHeight();
int width = getWidth();
int size;
if (SIZE_ERROR_PANEL_DYNAMICALLY) {
if (myErrorPanelHeight == -1) {
// Make the layout take up to 3/4ths of the height, and at least 1/4th, but
// anywhere in between based on what the actual text requires
size = height * 3 / 4;
int preferredHeight = myErrorPanel.getPreferredHeight(width) + 8;
if (preferredHeight < size) {
size = Math.max(preferredHeight, Math.min(height / 4, size));
myErrorPanelHeight = size;
}
} else {
size = myErrorPanelHeight;
}
} else {
size = height / 2;
}
myErrorPanel.setSize(width, size);
myErrorPanel.setLocation(0, height - size);
}
@Override
protected void paintComponent(Graphics graphics) {
paintRenderedImage(this, graphics, myImagePanel.getX(), myImagePanel.getY());
if (myPreviewManager != null) {
myPreviewManager.paint((Graphics2D)graphics);
}
super.paintComponent(graphics);
}
@Override
public Dimension getPreferredSize() {
return myImagePanel.getSize();
}
}
/**
* Panel which displays the progress icon. The progress icon can either be a large icon in the
* center, when there is no rendering showing, or a small icon in the upper right corner when there
* is a rendering. This is necessary because even though the progress icon looks good on some
* renderings, depending on the layout theme colors it is invisible in other cases.
*/
private class MyProgressPanel extends JPanel {
private AsyncProcessIcon mySmallProgressIcon;
private AsyncProcessIcon myLargeProgressIcon;
private boolean mySmall;
private boolean myProgressVisible;
private MyProgressPanel() {
super(new BorderLayout());
setOpaque(false);
}
/** The "small" icon mode isn't just for the icon size; it's for the layout position too; see {@link #doLayout} */
private void setSmallIcon(boolean small) {
if (small != mySmall) {
if (myProgressVisible && getComponentCount() != 0) {
AsyncProcessIcon oldIcon = getProgressIcon();
oldIcon.suspend();
}
mySmall = true;
removeAll();
AsyncProcessIcon icon = getProgressIcon();
add(icon, BorderLayout.CENTER);
if (myProgressVisible) {
icon.setVisible(true);
icon.resume();
}
}
}
public void showProgressIcon() {
if (!myProgressVisible) {
setSmallIcon(myRenderResult != null && myRenderResult.getImage() != null);
myProgressVisible = true;
setVisible(true);
AsyncProcessIcon icon = getProgressIcon();
if (getComponentCount() == 0) { // First time: haven't added icon yet?
add(getProgressIcon(), BorderLayout.CENTER);
} else {
icon.setVisible(true);
}
icon.resume();
}
}
public void hideProgressIcon() {
if (myProgressVisible) {
myProgressVisible = false;
setVisible(false);
AsyncProcessIcon icon = getProgressIcon();
icon.setVisible(false);
icon.suspend();
}
}
@Override
public void doLayout() {
super.doLayout();
if (!myProgressVisible) {
return;
}
// Place the progress icon in the center if there's no rendering, and in the
// upper right corner if there's a rendering. The reason for this is that the icon color
// will depend on whether we're in a light or dark IDE theme, and depending on the rendering
// in the layout it will be invisible. For example, in Darcula the icon is white, and if the
// layout is rendering a white screen, the progress is invisible.
AsyncProcessIcon icon = getProgressIcon();
Dimension size = icon.getPreferredSize();
if (mySmall) {
icon.setBounds(getWidth() - size.width - 1, 1, size.width, size.height);
} else {
icon.setBounds(getWidth() / 2 - size.width / 2, getHeight() / 2 - size.height / 2, size.width, size.height);
}
}
@Override
public Dimension getPreferredSize() {
return getProgressIcon().getPreferredSize();
}
@NotNull
private AsyncProcessIcon getProgressIcon() {
return getProgressIcon(mySmall);
}
@NotNull
private AsyncProcessIcon getProgressIcon(boolean small) {
if (small) {
if (mySmallProgressIcon == null) {
mySmallProgressIcon = new AsyncProcessIcon("Android layout rendering");
Disposer.register(RenderedPanel.this, mySmallProgressIcon);
}
return mySmallProgressIcon;
}
else {
if (myLargeProgressIcon == null) {
myLargeProgressIcon = new AsyncProcessIcon.Big("Android layout rendering");
Disposer.register(RenderedPanel.this, myLargeProgressIcon);
}
return myLargeProgressIcon;
}
}
}
}