1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.eclipse.org/org/documents/epl-v10.php
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
18
19import com.android.annotations.VisibleForTesting;
20
21import org.eclipse.swt.SWT;
22import org.eclipse.swt.graphics.Color;
23import org.eclipse.swt.graphics.GC;
24import org.eclipse.swt.graphics.Image;
25import org.eclipse.swt.graphics.Rectangle;
26
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.List;
30
31/**
32 * The {@link IncludeOverlay} class renders masks to -partially- hide everything outside
33 * an included file's own content. This overlay is in use when you are editing an included
34 * file shown within a different file's context (e.g. "Show In > other").
35 */
36public class IncludeOverlay extends Overlay {
37    /** Mask transparency - 0 is transparent, 255 is opaque */
38    private static final int MASK_TRANSPARENCY = 160;
39
40    /** The associated {@link LayoutCanvas}. */
41    private LayoutCanvas mCanvas;
42
43    /**
44     * Constructs an {@link IncludeOverlay} tied to the given canvas.
45     *
46     * @param canvas The {@link LayoutCanvas} to paint the overlay over.
47     */
48    public IncludeOverlay(LayoutCanvas canvas) {
49        mCanvas = canvas;
50    }
51
52    @Override
53    public void paint(GC gc) {
54        ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
55        List<Rectangle> includedBounds = viewHierarchy.getIncludedBounds();
56        if (includedBounds == null || includedBounds.size() == 0) {
57            // We don't support multiple included children yet. When that works,
58            // this code should use a BSP tree to figure out which regions to paint
59            // to leave holes in the mask.
60            return;
61        }
62
63        Image image = mCanvas.getImageOverlay().getImage();
64        if (image == null) {
65            return;
66        }
67
68        int oldAlpha = gc.getAlpha();
69        gc.setAlpha(MASK_TRANSPARENCY);
70        Color bg = gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
71        gc.setBackground(bg);
72
73        CanvasViewInfo root = viewHierarchy.getRoot();
74        Rectangle whole = root.getAbsRect();
75        whole = new Rectangle(whole.x, whole.y, whole.width + 1, whole.height + 1);
76        Collection<Rectangle> masks = subtractRectangles(whole, includedBounds);
77
78        for (Rectangle mask : masks) {
79            ControlPoint topLeft = LayoutPoint.create(mCanvas, mask.x, mask.y).toControl();
80            ControlPoint bottomRight = LayoutPoint.create(mCanvas, mask.x + mask.width,
81                    mask.y + mask.height).toControl();
82            int x1 = topLeft.x;
83            int y1 = topLeft.y;
84            int x2 = bottomRight.x;
85            int y2 = bottomRight.y;
86
87            gc.fillRectangle(x1, y1, x2 - x1, y2 - y1);
88        }
89
90        gc.setAlpha(oldAlpha);
91    }
92
93    /**
94     * Given a Rectangle, remove holes from it (specified as a collection of Rectangles) such
95     * that the result is a list of rectangles that cover everything that is not a hole.
96     *
97     * @param rectangle the rectangle to subtract from
98     * @param holes the holes to subtract from the rectangle
99     * @return a list of sub rectangles that remain after subtracting out the given list of holes
100     */
101    @VisibleForTesting
102    static Collection<Rectangle> subtractRectangles(
103            Rectangle rectangle, Collection<Rectangle> holes) {
104        List<Rectangle> result = new ArrayList<Rectangle>();
105        result.add(rectangle);
106
107        for (Rectangle hole : holes) {
108            List<Rectangle> tempResult = new ArrayList<Rectangle>();
109            for (Rectangle r : result) {
110                if (hole.intersects(r)) {
111                    // Clip the hole to fit the rectangle bounds
112                    Rectangle h = hole.intersection(r);
113
114                    // Split the rectangle
115
116                    // Above (includes the NW and NE corners)
117                    if (h.y > r.y) {
118                        tempResult.add(new Rectangle(r.x, r.y, r.width, h.y - r.y));
119                    }
120
121                    // Left (not including corners)
122                    if (h.x > r.x) {
123                        tempResult.add(new Rectangle(r.x, h.y, h.x - r.x, h.height));
124                    }
125
126                    int hx2 = h.x + h.width;
127                    int hy2 = h.y + h.height;
128                    int rx2 = r.x + r.width;
129                    int ry2 = r.y + r.height;
130
131                    // Below (includes the SW and SE corners)
132                    if (hy2 < ry2) {
133                        tempResult.add(new Rectangle(r.x, hy2, r.width, ry2 - hy2));
134                    }
135
136                    // Right (not including corners)
137                    if (hx2 < rx2) {
138                        tempResult.add(new Rectangle(hx2, h.y, rx2 - hx2, h.height));
139                    }
140                } else {
141                    tempResult.add(r);
142                }
143            }
144
145            result = tempResult;
146        }
147
148        return result;
149    }
150}
151