| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * 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.intellij.android.designer.designSurface; |
| |
| import com.android.annotations.VisibleForTesting; |
| import com.android.tools.idea.rendering.Overlay; |
| import com.android.tools.idea.rendering.RenderResult; |
| import com.android.tools.idea.rendering.RenderedImage; |
| import com.google.common.collect.Lists; |
| import com.intellij.android.designer.designSurface.graphics.DesignerGraphics; |
| import com.intellij.android.designer.designSurface.graphics.DrawingStyle; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.image.BufferedImage; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Root component used for the Android designer. |
| */ |
| public class RootView extends JComponent implements TransformedComponent { |
| public static final int EMPTY_COMPONENT_SIZE = 5; |
| public static final int VISUAL_EMPTY_COMPONENT_SIZE = 14; |
| |
| @Nullable private List<EmptyRegion> myEmptyRegions; |
| @NotNull private final AndroidDesignerEditorPanel myPanel; |
| protected int myX; |
| protected int myY; |
| @Nullable RenderedImage myRenderedImage; |
| |
| public RootView(@NotNull AndroidDesignerEditorPanel panel, int x, int y, @NotNull RenderResult renderResult) { |
| myX = x; |
| myY = y; |
| myPanel = panel; |
| myRenderedImage = renderResult.getImage(); |
| } |
| |
| @NotNull |
| public AndroidDesignerEditorPanel getPanel() { |
| return myPanel; |
| } |
| |
| @Nullable |
| public BufferedImage getImage() { |
| return myRenderedImage != null ? myRenderedImage.getOriginalImage() : null; |
| } |
| |
| @Nullable |
| public RenderedImage getRenderedImage() { |
| return myRenderedImage; |
| } |
| |
| /** |
| * Sets the image to be drawn |
| * <p> |
| * The image <b>can</b> be null, which is the case when we are dealing with |
| * an empty document. |
| * |
| * @param image The image to be rendered |
| */ |
| public void setRenderedImage(@Nullable RenderedImage image) { |
| clearEmptyRegions(); |
| myRenderedImage = image; |
| updateBounds(true); |
| repaint(); |
| } |
| |
| /** |
| * Returns whether this image overlay should be painted with a drop shadow. |
| * This is usually the case, but not for transparent themes like the dialog |
| * theme (Theme.*Dialog), which already provides its own shadow. |
| * |
| * @return true if the image overlay should be shown with a drop shadow. |
| */ |
| public boolean getShowDropShadow() { |
| if (myRenderedImage != null) { |
| return myRenderedImage.getShowDropShadow(); |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| public void paintComponent(Graphics g) { |
| super.paintComponent(g); |
| paintImage(g); |
| } |
| |
| public void updateSize() { |
| updateBounds(true); |
| } |
| |
| protected void updateBounds(boolean imageChanged) { |
| if (myRenderedImage == null) { |
| return; |
| } |
| if (myPanel.isZoomToFit()) { |
| myPanel.zoomToFitIfNecessary(); |
| } |
| |
| double zoom = myPanel.getZoom(); |
| myRenderedImage.setScale(zoom); |
| Dimension requiredSize = myRenderedImage.getRequiredSize(); |
| int newWidth = requiredSize.width; |
| int newHeight = requiredSize.height; |
| if (getWidth() != newWidth || getHeight() != newHeight) { |
| setSize(newWidth, newHeight); |
| myRenderedImage.imageChanged(); |
| } else if (imageChanged) { |
| myRenderedImage.imageChanged(); |
| } |
| } |
| |
| public void clearEmptyRegions() { |
| myEmptyRegions = null; |
| } |
| |
| public void addEmptyRegion(int x, int y, int width, int height) { |
| if (myRenderedImage == null) { |
| return; |
| } |
| BufferedImage image = myRenderedImage.getOriginalImage(); |
| int imageWidth = image.getWidth(); |
| int imageHeight = image.getHeight(); |
| if (x >= 0 && x <= imageWidth && y >= 0 && y <= imageHeight) { |
| EmptyRegion r = new EmptyRegion(); |
| r.myX = Math.max(0, Math.min(x, imageWidth - VISUAL_EMPTY_COMPONENT_SIZE)); |
| r.myY = Math.max(0, Math.min(y, imageHeight - VISUAL_EMPTY_COMPONENT_SIZE)); |
| r.myWidth = width; |
| r.myHeight = height; |
| //noinspection UseJBColor |
| r.myColor = new Color(~image.getRGB(r.myX, r.myY)); |
| if (myEmptyRegions == null) { |
| myEmptyRegions = new ArrayList<EmptyRegion>(); |
| } |
| myEmptyRegions.add(r); |
| } |
| } |
| |
| protected void paintImage(Graphics g) { |
| if (myRenderedImage == null) { |
| return; |
| } |
| |
| Shape clip = g.getClip(); |
| if (clip != null) { |
| Rectangle clipBounds = g.getClipBounds(); |
| int deltaX = getX(); |
| int deltaY = getY(); |
| g.setClip(clipBounds.x - deltaX, clipBounds.y - deltaY, clipBounds.width += deltaX, clipBounds.height + deltaY); |
| } |
| |
| double scale = myPanel.getZoom(); |
| myRenderedImage.setScale(scale); |
| myRenderedImage.paint(g, 0, 0); |
| |
| if (myEmptyRegions != null && !myEmptyRegions.isEmpty()) { |
| if (scale == 1) { |
| for (EmptyRegion r : myEmptyRegions) { |
| DesignerGraphics.drawFilledRect(DrawingStyle.EMPTY, g, r.myX, r.myY, r.myWidth, r.myHeight); |
| } |
| } else { |
| for (EmptyRegion r : myEmptyRegions) { |
| DesignerGraphics.drawFilledRect(DrawingStyle.EMPTY, g, (int)(scale * r.myX), (int)(scale * r.myY), |
| (int)(scale * r.myWidth), (int)(scale * r.myHeight)); |
| } |
| } |
| } |
| |
| Overlay.paintOverlays(myPanel, this, g, 0, 0); |
| |
| if (clip != null) { |
| g.setClip(clip); |
| } |
| } |
| |
| /** Returns the width of the image itself, when scaled */ |
| public int getScaledWidth() { |
| if (myRenderedImage != null) { |
| myRenderedImage.setScale(myPanel.getZoom()); |
| return myRenderedImage.getScaledWidth(); |
| } |
| |
| return 0; |
| } |
| |
| /** Returns the height of the image itself, when scaled */ |
| public int getScaledHeight() { |
| if (myRenderedImage != null) { |
| myRenderedImage.setScale(myPanel.getZoom()); |
| return myRenderedImage.getScaledHeight(); |
| } |
| |
| return 0; |
| } |
| |
| // Implements ScalableComponent |
| |
| @Override |
| public double getScale() { |
| double zoom = myPanel.getZoom(); |
| |
| if (myRenderedImage != null) { |
| Rectangle viewBounds = myRenderedImage.getImageBounds(); |
| if (viewBounds != null) { |
| double deviceFrameFactor = viewBounds.getWidth() / (double) myRenderedImage.getScaledWidth(); |
| if (deviceFrameFactor != 1) { |
| zoom *= deviceFrameFactor; |
| } |
| } |
| } |
| return zoom; |
| } |
| |
| // Implements TransformedComponent |
| |
| @Override |
| public int getShiftX() { |
| if (myRenderedImage != null) { |
| Rectangle viewBounds = myRenderedImage.getImageBounds(); |
| if (viewBounds != null) { |
| return viewBounds.x; |
| } |
| } |
| return 0; |
| } |
| |
| @Override |
| public int getShiftY() { |
| if (myRenderedImage != null) { |
| Rectangle viewBounds = myRenderedImage.getImageBounds(); |
| if (viewBounds != null) { |
| return viewBounds.y; |
| } |
| } |
| return 0; |
| } |
| |
| @VisibleForTesting |
| public List<Rectangle> getEmptyRegions() { |
| List<Rectangle> list = Lists.newArrayList(); |
| if (myEmptyRegions != null) { |
| for (EmptyRegion region : myEmptyRegions) { |
| list.add(new Rectangle(region.myX, region.myY, region.myWidth, region.myHeight)); |
| } |
| } |
| return list; |
| } |
| |
| private static class EmptyRegion { |
| public Color myColor; |
| public int myX; |
| public int myY; |
| public int myWidth; |
| public int myHeight; |
| } |
| } |