/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php * * 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.adt.gscripts; /** * An {@link IViewRule} for android.widget.RelativeLayout and all its derived classes. */ public class AndroidWidgetRelativeLayoutRule extends BaseLayout { // ==== Selection ==== /** * Display some relation layout information on a selected child. */ void onChildSelected(IGraphics gc, INode parentNode, INode childNode) { super.onChildSelected(gc, parentNode, childNode); // Get the top parent, to display data under it INode topParent = parentNode; while (true) { INode p = topParent.getParent(); if (p == null) { break; } else { topParent = p; } } Rect b = topParent.getBounds(); if (!b.isValid()) { return; } def infos = []; def addAttr = { def a = childNode.getStringAttr("layout_${it}"); if (a) { infos += "${it}: ${a}"; } } addAttr("above"); addAttr("below"); addAttr("toLeftOf"); addAttr("toRightOf"); addAttr("alignBaseline"); addAttr("alignTop"); addAttr("alignBottom"); addAttr("alignLeft"); addAttr("alignRight"); addAttr("alignParentTop"); addAttr("alignParentBottom"); addAttr("alignParentLeft"); addAttr("alignParentRight"); addAttr("alignWithParentMissing"); addAttr("centerHorizontal"); addAttr("centerInParent"); addAttr("centerVertical"); if (infos) { gc.setForeground(gc.registerColor(0x00222222)); int x = b.x + 10; int y = b.y + b.h + 10; int h = gc.getFontHeight(); infos.each { y += h; gc.drawString(it, x, y); } } } // ==== Drag'n'drop support ==== DropFeedback onDropEnter(INode targetNode, String fqcn) { def bn = targetNode.getBounds(); if (!bn.isValid()) { return; } // Prepare the drop feedback return new DropFeedback( [ "child": null, // INode: Current child under cursor "index": 0, // int: Index of child in the parent children list "zones": null, // Valid "anchor" zones for the current child // of type list(map(rect:Rect, attr:[String])) "curr": null, // map: Current zone ], { gc, node, feedback -> // Paint closure for the RelativeLayout just defers to the method below drawRelativeDropFeedback(gc, node, feedback); }); } DropFeedback onDropMove(INode layoutNode, String fqcn, DropFeedback feedback, Point p) { def data = feedback.userData; Rect area = feedback.captureArea; // Only look for a new child if cursor is no longer under the current rect if (area == null || !area.contains(p)) { // We're not capturing anymore since we got outside of the capture bounds feedback.captureArea = null; // Find the current direct children under the cursor def childNode = null; def childIndex = -1; for(child in layoutNode.getChildren()) { childIndex++; if (child.getBounds().contains(p)) { childNode = child; break; } } // If there is a selected child and it changed, recompute child drop zones if (childNode != null && childNode != data.child) { data.child = childNode; data.index = childIndex; data.curr = null; data.zones = null; def result = computeChildDropZones(childNode); data.zones = result[1]; // capture this rect, to prevent the engine from switching the layout node. feedback.captureArea = result[0]; feedback.requestPaint = true; } else if (childNode == null) { // If there is no selected child, compute the border drop zone data.child = null; data.index = -1; data.curr = null; def zone = computeBorderDropZone(layoutNode.getBounds(), p); if (zone == null) { data.zones = null; } else { data.zones = [ zone ]; feedback.captureArea = zone.rect; } feedback.requestPaint = (area != feedback.captureArea); } } // Find the current zone def currZone = null; if (data.zones) { for(zone in data.zones) { if (zone.rect.contains(p)) { currZone = zone; break; } } } if (currZone != data.curr) { data.curr = currZone; feedback.requestPaint = true; } return feedback; } def computeBorderDropZone(Rect bounds, Point p) { int x = p.x; int y = p.y; int x1 = bounds.x; int y1 = bounds.y; int w = bounds.w; int h = bounds.h; int x2 = x1 + w; int y2 = y1 + h; int n = 10; int n2 = n*2; Rect r = null; String attr = null; if (x <= x1 + n && y >= y1 && y <= y2) { r = new Rect(x1 - n, y1, n2, h); attr = "alignParentLeft"; } else if (x >= x2 - n && y >= y1 && y <= y2) { r = new Rect(x2 - n, y1, n2, h); attr = "alignParentRight"; } else if (y <= y1 + n && x >= x1 && x <= x2) { r = new Rect(x1, y1 - n, w, n2); attr = "alignParentTop"; } else if (y >= y2 - n && x >= x1 && x <= x2) { r = new Rect(x1, y2 - n, w, n2); attr = "alignParentBottom"; } else { // we're nowhere near a border return null; } return [ "rect": r, "attr": [ attr ], "mark": r.center() ]; } def computeChildDropZones(INode childNode) { Rect b = childNode.getBounds(); // Compute drop zone borders as follow: // // +---+-----+-----+-----+---+ // | 1 \ 2 \ 3 / 4 / 5 | // +----+-----+---+-----+----+ // // For the top and bottom borders, zones 1 and 5 have the same width, which is // size1 = min(10, w/5) // and zones 2, 3 and 4 have a width of // size2 = (w - 2*size) / 3 // // Same works for left and right borders vertically. // // Attributes generated: // Horizontally: // 1- toLeftOf / 2- alignLeft / 3- 2+4 / 4- alignRight / 5- toRightOf // Vertically: // 1- above / 2-alignTop / 3- 2+4 / 4- alignBottom / 5- below int w1 = 20; int w3 = b.w / 3; int w2 = Math.max(20, w3); int h1 = 20; int h3 = b.h / 3; int h2 = Math.max(20, h3); int wt = w1 * 2 + w2 * 3; int ht = h1 * 2 + h2 * 3; int x1 = b.x + ((b.w - wt) / 2); int y1 = b.y + ((b.h - ht) / 2); def bounds = new Rect(x1, y1, wt, ht); def zones = []; def a = "above"; int x = x1; int y = y1; def addx = { int wn, ArrayList a2 -> zones << [ "rect": new Rect(x, y, wn, h1), "attr": [ a ] + a2 ]; x += wn; } addx(w1, [ "toLeftOf" ]); addx(w2, [ "alignLeft" ]); addx(w2, [ "alignLeft", "alignRight" ]); addx(w2, [ "alignRight" ]); addx(w1, [ "toRightOf" ]); a = "below"; x = x1; y = y1 + ht - h1; addx(w1, [ "toLeftOf" ]); addx(w2, [ "alignLeft" ]); addx(w2, [ "alignLeft", "alignRight" ]); addx(w2, [ "alignRight" ]); addx(w1, [ "toRightOf" ]); def addy = { int hn, ArrayList a2 -> zones << [ "rect": new Rect(x, y, w1, hn), "attr": [ a ] + a2 ]; y += hn; } a = "toLeftOf"; x = x1; y = y1 + h1; addy(h2, [ "alignTop" ]); addy(h2, [ "alignTop", "alignBottom" ]); addy(h2, [ "alignBottom" ]); a = "toRightOf"; x = x1 + wt - w1; y = y1 + h1; addy(h2, [ "alignTop" ]); addy(h2, [ "alignTop", "alignBottom" ]); addy(h2, [ "alignBottom" ]); return [ bounds, zones ]; } void drawRelativeDropFeedback(IGraphics gc, INode layoutNode, DropFeedback feedback) { Rect b = layoutNode.getBounds(); if (!b.isValid()) { return; } def color = gc.registerColor(0x00FF9900); gc.setForeground(color); gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID); gc.setLineWidth(2); gc.drawRect(b); gc.setLineStyle(IGraphics.LineStyle.LINE_DOT); gc.setLineWidth(1); def data = feedback.userData; if (data.zones) { data.zones.each { gc.drawRect(it.rect); } } if (data.curr) { gc.setAlpha(200); gc.setBackground(color); gc.fillRect(data.curr.rect); gc.setAlpha(255); def r = feedback.captureArea; int x = r.x + 5; int y = r.y + r.h + 5; int h = gc.getFontHeight(); String id = null; if (data.child) { id = data.child.getStringAttr("id"); } data.curr.attr.each { String s = it; if (id) s = "$s=$id"; gc.drawString(s, x, y); y += h; } def mark = data.curr.get("mark"); if (mark) { gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID); gc.setLineWidth(2); def black = gc.registerColor(0); gc.setForeground(black); x = mark.x; y = mark.y; gc.drawLine(x - 10, y - 10, x + 10, y + 10); gc.drawLine(x + 10, y - 10, x - 10, y + 10); gc.drawRect(x - 10, y - 10, x + 10, y + 10); } } } void onDropLeave(INode targetNode, String fqcn, DropFeedback feedback) { // Free the last captured rect, if any feedback.captureArea = null; } void onDropped(INode targetNode, String fqcn, DropFeedback feedback, Point p) { def data = feedback.userData; if (!data.curr) { return; } def index = data.index; targetNode.debugPrintf("Relative.drop: add ${fqcn} after index ${index}"); // Get the last component of the FQCN (e.g. "android.view.Button" => "Button") String name = fqcn; name = name[name.lastIndexOf(".")+1 .. name.length()-1]; targetNode.editXml("Add ${name} to RelativeLayout") { INode e = targetNode.insertChildAt(fqcn, index + 1); String id = null; if (data.child) { id = data.child.getStringAttr("id"); } data.curr.attr.each { e.setAttribute("layout_${it}", id ? id : "true"); } } } }