Search in sources :

Example 1 with SegmentType

use of com.android.tools.idea.uibuilder.model.SegmentType in project android by JetBrains.

the class ConstraintPainter method paintVerticalConstraint.

/**
   * Paints a vertical constraint, handling the various scenarios where there are
   * margins, or where the two nodes overlap horizontally and where they don't, etc.
   * <p/>
   * Here's an example of what will be shown for a "below" constraint where the
   * nodes do not overlap horizontally and the target node has a bottom margin:
   * <pre>
   *  +--------+
   *  | Target |
   *  +--------+
   *       |
   *       v
   *   - - - - - - - - - - - - - -
   *                         ^
   *                         |
   *                    +--------+
   *                    | Source |
   *                    +--------+
   * </pre>
   */
private static void paintVerticalConstraint(NlGraphics graphics, ConstraintType type, NlComponent sourceNode, Rectangle sourceBounds, NlComponent targetNode, Rectangle targetBounds, boolean highlightTargetEdge) {
    SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
    SegmentType targetSegmentTypeY = type.targetSegmentTypeY;
    Insets targetMargins = targetNode.getMargins();
    assert sourceSegmentTypeY != SegmentType.UNKNOWN;
    assert targetBounds != null;
    int sourceY = sourceSegmentTypeY.getY(sourceNode, sourceBounds);
    int targetY = targetSegmentTypeY == SegmentType.UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds);
    if (highlightTargetEdge && type.isRelativeToParentEdge()) {
        graphics.useStyle(DROP_ZONE_ACTIVE);
        graphics.fillRect(targetBounds.x, targetY - PARENT_RECT_SIZE / 2, targetBounds.width, PARENT_RECT_SIZE);
    }
    // First see if the two views overlap horizontally. If so, we can just draw a direct
    // arrow from the source up to (or down to) the target.
    //
    //  +--------+
    //  | Target |
    //  +--------+
    //         ^
    //         |
    //         |
    //       +--------+
    //       | Source |
    //       +--------+
    //
    int maxLeft = Math.max(sourceBounds.x, targetBounds.x);
    int minRight = Math.min(x2(sourceBounds), x2(targetBounds));
    int center = (maxLeft + minRight) / 2;
    if (center > sourceBounds.x && center < x2(sourceBounds)) {
        // for small margins
        if (targetSegmentTypeY == SegmentType.BOTTOM && targetMargins.bottom > 5) {
            int sharedY = targetY + targetMargins.bottom;
            if (sourceY > sharedY + 2) {
                // Skip when source falls on the margin line
                graphics.useStyle(GUIDELINE_DASHED);
                graphics.drawLine(targetBounds.x, sharedY, x2(targetBounds), sharedY);
                graphics.useStyle(GUIDELINE);
                graphics.drawArrow(center, sourceY, center, sharedY + 2);
                graphics.drawArrow(center, targetY, center, sharedY - 3);
            } else {
                graphics.useStyle(GUIDELINE);
                // Draw reverse arrow to make it clear the node is as close
                // at it can be
                graphics.drawArrow(center, targetY, center, sourceY);
            }
            return;
        } else if (targetSegmentTypeY == SegmentType.TOP && targetMargins.top > 5) {
            int sharedY = targetY - targetMargins.top;
            if (sourceY < sharedY - 2) {
                graphics.useStyle(GUIDELINE_DASHED);
                graphics.drawLine(targetBounds.x, sharedY, x2(targetBounds), sharedY);
                graphics.useStyle(GUIDELINE);
                graphics.drawArrow(center, sourceY, center, sharedY - 3);
                graphics.drawArrow(center, targetY, center, sharedY + 3);
            } else {
                graphics.useStyle(GUIDELINE);
                graphics.drawArrow(center, targetY, center, sourceY);
            }
            return;
        }
        // direction
        if (sourceY == targetY) {
            if (sourceSegmentTypeY == SegmentType.BOTTOM || sourceSegmentTypeY == SegmentType.BASELINE) {
                sourceY -= 2 * ARROW_SIZE;
            } else if (sourceSegmentTypeY == SegmentType.TOP) {
                sourceY += 2 * ARROW_SIZE;
            } else {
                assert sourceSegmentTypeY == SegmentType.CENTER_HORIZONTAL : sourceSegmentTypeY;
                sourceY += sourceBounds.height / 2 - 2 * ARROW_SIZE;
            }
        } else if (sourceSegmentTypeY == SegmentType.BASELINE) {
            sourceY = targetY - 2 * ARROW_SIZE;
        }
        // Center the vertical line in the overlap region
        graphics.useStyle(GUIDELINE);
        graphics.drawArrow(center, sourceY, center, targetY);
        return;
    }
    // If there is no horizontal overlap in the vertical constraints, then we
    // will show the attachment relative to a dashed line that extends beyond
    // the target bounds, like this:
    //
    //  +--------+
    //  | Target |
    //  +--------+ - - - - - - - - -
    //                         ^
    //                         |
    //                    +--------+
    //                    | Source |
    //                    +--------+
    //
    // However, if the target node has a vertical margin, we may need to offset
    // the line:
    //
    //  +--------+
    //  | Target |
    //  +--------+
    //       |
    //       v
    //   - - - - - - - - - - - - - -
    //                         ^
    //                         |
    //                    +--------+
    //                    | Source |
    //                    +--------+
    //
    // If not, we'll need to indicate a shared edge. This is the edge that separate
    // them (but this will require me to evaluate margins!)
    // Compute overlap region and pick the middle
    int sharedY = targetSegmentTypeY == SegmentType.UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds);
    if (type.relativeToMargin) {
        if (targetSegmentTypeY == SegmentType.TOP) {
            sharedY -= targetMargins.top;
        } else if (targetSegmentTypeY == SegmentType.BOTTOM) {
            sharedY += targetMargins.bottom;
        }
    }
    int startX;
    int endX;
    if (center <= sourceBounds.x) {
        startX = targetBounds.x + targetBounds.width / 4;
        endX = x2(sourceBounds);
    } else {
        assert (center >= x2(sourceBounds));
        startX = sourceBounds.x;
        endX = targetBounds.x + 3 * targetBounds.width / 4;
    }
    // Must draw segmented line instead
    // Place the arrow 1/4 instead of 1/2 in the source to avoid overlapping with the
    // selection handles
    graphics.useStyle(GUIDELINE_DASHED);
    graphics.drawLine(startX, sharedY, endX, sharedY);
    // should point directly at the edge
    if (Math.abs(sharedY - sourceY) < 2 * ARROW_SIZE) {
        if (sourceSegmentTypeY == SegmentType.BASELINE) {
            sourceY = sharedY - 2 * ARROW_SIZE;
        } else if (sourceSegmentTypeY == SegmentType.TOP) {
            sharedY = sourceY;
            sourceY = sharedY + 2 * ARROW_SIZE;
        } else {
            sharedY = sourceY;
            sourceY = sharedY - 2 * ARROW_SIZE;
        }
    }
    graphics.useStyle(GUIDELINE);
    // Draw the line from the source anchor to the shared edge
    int x = sourceBounds.x + ((sourceSegmentTypeY == SegmentType.BASELINE) ? sourceBounds.width / 2 : sourceBounds.width / 4);
    graphics.drawArrow(x, sourceY, x, sharedY);
    // Draw the line from the target to the horizontal shared edge
    int tx = centerX(targetBounds);
    if (targetSegmentTypeY == SegmentType.TOP) {
        int ty = targetBounds.y;
        int margin = targetMargins.top;
        if (margin == 0 || !type.relativeToMargin) {
            graphics.drawArrow(tx, ty + 2 * ARROW_SIZE, tx, ty);
        } else {
            graphics.drawArrow(tx, ty, tx, ty - margin);
        }
    } else if (targetSegmentTypeY == SegmentType.BOTTOM) {
        int ty = y2(targetBounds);
        int margin = targetMargins.bottom;
        if (margin == 0 || !type.relativeToMargin) {
            graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty);
        } else {
            graphics.drawArrow(tx, ty, tx, ty + margin);
        }
    } else {
        assert targetSegmentTypeY == SegmentType.BASELINE : targetSegmentTypeY;
        int ty = targetSegmentTypeY.getY(targetNode, targetBounds);
        graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty);
    }
}
Also used : SegmentType(com.android.tools.idea.uibuilder.model.SegmentType) Insets(com.android.tools.idea.uibuilder.model.Insets)

Example 2 with SegmentType

use of com.android.tools.idea.uibuilder.model.SegmentType in project android by JetBrains.

the class ConstraintPainter method paintCornerConstraint.

/**
   * Paints a corner constraint, or returns false if this constraint is not a corner.
   * A corner is one where there are two constraints from this source node to the
   * same target node, one horizontal and one vertical, to the closest edges on
   * the target node.
   * <p/>
   * Corners are a common occurrence. If we treat the horizontal and vertical
   * constraints separately (below & toRightOf), then we end up with a lot of
   * extra lines and arrows -- e.g. two shared edges and arrows pointing to these
   * shared edges:
   * <p/>
   * <pre>
   *  +--------+ |
   *  | Target -->
   *  +----|---+ |
   *       v
   *  - - - - - -|- - - - - -
   *                   ^
   *             | +---|----+
   *             <-- Source |
   *             | +--------+
   *
   * Instead, we can simply draw a diagonal arrow here to represent BOTH constraints and
   * reduce clutter:
   *
   *  +---------+
   *  | Target _|
   *  +-------|\+
   *            \
   *             \--------+
   *             | Source |
   *             +--------+
   * </pre>
   *
   * @param graphics       the graphics context to draw
   * @param type           the constraint to be drawn
   * @param sourceNode     the source node
   * @param sourceBounds   the bounds of the source node
   * @param targetNode     the target node
   * @param targetBounds   the bounds of the target node
   * @param allConstraints the set of all constraints; if a corner is found and painted the
   *                       matching corner constraint is removed from the set
   * @return true if the constraint was handled and painted as a corner, false otherwise
   */
@SuppressWarnings("PointlessArithmeticExpression")
private static boolean paintCornerConstraint(NlGraphics graphics, ConstraintType type, NlComponent sourceNode, Rectangle sourceBounds, NlComponent targetNode, Rectangle targetBounds, Set<DependencyGraph.Constraint> allConstraints, TextDirection textDirection) {
    SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
    SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
    SegmentType targetSegmentTypeX = type.targetSegmentTypeX;
    SegmentType targetSegmentTypeY = type.targetSegmentTypeY;
    ConstraintType opposite1, opposite2;
    switch(type) {
        case LAYOUT_BELOW:
        case LAYOUT_ABOVE:
            opposite1 = ConstraintType.LAYOUT_LEFT_OF;
            opposite2 = ConstraintType.LAYOUT_RIGHT_OF;
            break;
        case LAYOUT_LEFT_OF:
        case LAYOUT_RIGHT_OF:
            opposite1 = ConstraintType.LAYOUT_ABOVE;
            opposite2 = ConstraintType.LAYOUT_BELOW;
            break;
        default:
            return false;
    }
    DependencyGraph.Constraint pair = null;
    for (DependencyGraph.Constraint constraint : allConstraints) {
        if ((constraint.type == opposite1 || constraint.type == opposite2) && constraint.to.node == targetNode && constraint.from.node == sourceNode) {
            pair = constraint;
            break;
        }
    }
    if (pair != null) {
        // Visualize the corner constraint
        if (sourceSegmentTypeX == SegmentType.UNKNOWN) {
            sourceSegmentTypeX = pair.type.sourceSegmentTypeX;
        }
        if (sourceSegmentTypeY == SegmentType.UNKNOWN) {
            sourceSegmentTypeY = pair.type.sourceSegmentTypeY;
        }
        if (targetSegmentTypeX == SegmentType.UNKNOWN) {
            targetSegmentTypeX = pair.type.targetSegmentTypeX;
        }
        if (targetSegmentTypeY == SegmentType.UNKNOWN) {
            targetSegmentTypeY = pair.type.targetSegmentTypeY;
        }
        int x1, y1, x2, y2;
        if (textDirection.isLeftSegment(sourceSegmentTypeX)) {
            x1 = sourceBounds.x + 1 * sourceBounds.width / 4;
        } else {
            x1 = sourceBounds.x + 3 * sourceBounds.width / 4;
        }
        if (sourceSegmentTypeY == SegmentType.TOP) {
            y1 = sourceBounds.y + 1 * sourceBounds.height / 4;
        } else {
            y1 = sourceBounds.y + 3 * sourceBounds.height / 4;
        }
        if (textDirection.isLeftSegment(targetSegmentTypeX)) {
            x2 = targetBounds.x + 1 * targetBounds.width / 4;
        } else {
            x2 = targetBounds.x + 3 * targetBounds.width / 4;
        }
        if (targetSegmentTypeY == SegmentType.TOP) {
            y2 = targetBounds.y + 1 * targetBounds.height / 4;
        } else {
            y2 = targetBounds.y + 3 * targetBounds.height / 4;
        }
        graphics.useStyle(GUIDELINE);
        graphics.drawArrow(x1, y1, x2, y2);
        // Don't process this constraint on its own later.
        allConstraints.remove(pair);
        return true;
    }
    return false;
}
Also used : SegmentType(com.android.tools.idea.uibuilder.model.SegmentType)

Example 3 with SegmentType

use of com.android.tools.idea.uibuilder.model.SegmentType in project android by JetBrains.

the class ConstraintPainter method paintConstraint.

/**
   * Paints a constraint of the given type from the given source node, to the
   * given target node, with the specified bounds.
   */
private static void paintConstraint(NlGraphics graphics, ConstraintType type, NlComponent sourceNode, Rectangle sourceBounds, NlComponent targetNode, Rectangle targetBounds, @Nullable Set<DependencyGraph.Constraint> allConstraints, boolean highlightTargetEdge, TextDirection textDirection) {
    SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
    SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
    SegmentType targetSegmentTypeX = type.targetSegmentTypeX;
    SegmentType targetSegmentTypeY = type.targetSegmentTypeY;
    // Horizontal center constraint?
    if (sourceSegmentTypeX == SegmentType.CENTER_VERTICAL && targetSegmentTypeX == SegmentType.CENTER_VERTICAL) {
        paintHorizontalCenterConstraint(graphics, sourceBounds, targetBounds);
        return;
    }
    // Vertical center constraint?
    if (sourceSegmentTypeY == SegmentType.CENTER_HORIZONTAL && targetSegmentTypeY == SegmentType.CENTER_HORIZONTAL) {
        paintVerticalCenterConstraint(graphics, sourceBounds, targetBounds);
        return;
    }
    // Corner constraint?
    if (allConstraints != null && (type == ConstraintType.LAYOUT_ABOVE || type == ConstraintType.LAYOUT_BELOW || type == ConstraintType.LAYOUT_LEFT_OF || type == ConstraintType.LAYOUT_RIGHT_OF)) {
        if (paintCornerConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, allConstraints, textDirection)) {
            return;
        }
    }
    // Vertical constraint?
    if (sourceSegmentTypeX == SegmentType.UNKNOWN) {
        paintVerticalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, highlightTargetEdge);
        return;
    }
    // Horizontal constraint?
    if (sourceSegmentTypeY == SegmentType.UNKNOWN) {
        paintHorizontalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, highlightTargetEdge, textDirection);
        return;
    }
    // and is not a centering constraint
    assert false;
}
Also used : SegmentType(com.android.tools.idea.uibuilder.model.SegmentType)

Example 4 with SegmentType

use of com.android.tools.idea.uibuilder.model.SegmentType in project android by JetBrains.

the class ConstraintPainter method paintHorizontalConstraint.

/**
   * Paints a horizontal constraint, handling the various scenarios where there are margins,
   * or where the two nodes overlap horizontally and where they don't, etc.
   */
private static void paintHorizontalConstraint(NlGraphics graphics, ConstraintType type, NlComponent sourceNode, Rectangle sourceBounds, NlComponent targetNode, Rectangle targetBounds, boolean highlightTargetEdge, TextDirection textDirection) {
    SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
    SegmentType targetSegmentTypeX = type.targetSegmentTypeX;
    Insets targetMargins = targetNode.getMargins();
    assert sourceSegmentTypeX != SegmentType.UNKNOWN;
    assert targetBounds != null;
    // See paintVerticalConstraint for explanations of the various cases.
    int sourceX = sourceSegmentTypeX.getX(textDirection, sourceNode, sourceBounds);
    int targetX = targetSegmentTypeX == SegmentType.UNKNOWN ? sourceX : targetSegmentTypeX.getX(textDirection, targetNode, targetBounds);
    if (highlightTargetEdge && type.isRelativeToParentEdge()) {
        graphics.useStyle(DROP_ZONE_ACTIVE);
        graphics.fillRect(targetX - PARENT_RECT_SIZE / 2, targetBounds.y, PARENT_RECT_SIZE, targetBounds.height);
    }
    int maxTop = Math.max(sourceBounds.y, targetBounds.y);
    int minBottom = Math.min(y2(sourceBounds), y2(targetBounds));
    // First see if the two views overlap vertically. If so, we can just draw a direct
    // arrow from the source over to the target.
    int center = (maxTop + minBottom) / 2;
    if (center > sourceBounds.y && center < y2(sourceBounds)) {
        // See if we should draw a margin line
        if (textDirection.isRightSegment(targetSegmentTypeX) && targetMargins.right > 5) {
            int sharedX = targetX + targetMargins.right;
            if (sourceX > sharedX + 2) {
                // Skip when source falls on the margin line
                graphics.useStyle(GUIDELINE_DASHED);
                graphics.drawLine(sharedX, targetBounds.y, sharedX, y2(targetBounds));
                graphics.useStyle(GUIDELINE);
                graphics.drawArrow(sourceX, center, sharedX + 2, center);
                graphics.drawArrow(targetX, center, sharedX - 3, center);
            } else {
                graphics.useStyle(GUIDELINE);
                // Draw reverse arrow to make it clear the node is as close
                // at it can be
                graphics.drawArrow(targetX, center, sourceX, center);
            }
            return;
        } else if (textDirection.isLeftSegment(targetSegmentTypeX) && targetMargins.left > 5) {
            int sharedX = targetX - targetMargins.left;
            if (sourceX < sharedX - 2) {
                graphics.useStyle(GUIDELINE_DASHED);
                graphics.drawLine(sharedX, targetBounds.y, sharedX, y2(targetBounds));
                graphics.useStyle(GUIDELINE);
                graphics.drawArrow(sourceX, center, sharedX - 3, center);
                graphics.drawArrow(targetX, center, sharedX + 3, center);
            } else {
                graphics.useStyle(GUIDELINE);
                graphics.drawArrow(targetX, center, sourceX, center);
            }
            return;
        }
        if (sourceX == targetX) {
            if (textDirection.isRightSegment(sourceSegmentTypeX)) {
                sourceX -= 2 * ARROW_SIZE;
            } else if (textDirection.isLeftSegment(sourceSegmentTypeX)) {
                sourceX += 2 * ARROW_SIZE;
            } else {
                assert sourceSegmentTypeX == SegmentType.CENTER_VERTICAL : sourceSegmentTypeX;
                sourceX += sourceBounds.width / 2 - 2 * ARROW_SIZE;
            }
        }
        graphics.useStyle(GUIDELINE);
        graphics.drawArrow(sourceX, center, targetX, center);
        return;
    }
    // Segment line
    // Compute overlap region and pick the middle
    int sharedX = targetSegmentTypeX == SegmentType.UNKNOWN ? sourceX : targetSegmentTypeX.getX(textDirection, targetNode, targetBounds);
    if (type.relativeToMargin) {
        if (textDirection.isLeftSegment(targetSegmentTypeX)) {
            sharedX -= targetMargins.left;
        } else if (textDirection.isRightSegment(targetSegmentTypeX)) {
            sharedX += targetMargins.right;
        }
    }
    int startY, endY;
    if (center <= sourceBounds.y) {
        startY = targetBounds.y + targetBounds.height / 4;
        endY = y2(sourceBounds);
    } else {
        assert (center >= y2(sourceBounds));
        startY = sourceBounds.y;
        endY = targetBounds.y + 3 * targetBounds.height / 2;
    }
    // Must draw segmented line instead
    // Place at 1/4 instead of 1/2 to avoid overlapping with selection handles
    int y = sourceBounds.y + sourceBounds.height / 4;
    graphics.useStyle(GUIDELINE_DASHED);
    graphics.drawLine(sharedX, startY, sharedX, endY);
    // should point directly at the edge
    if (Math.abs(sharedX - sourceX) < 2 * ARROW_SIZE) {
        if (textDirection.isLeftSegment(sourceSegmentTypeX)) {
            sharedX = sourceX;
            sourceX = sharedX + 2 * ARROW_SIZE;
        } else {
            sharedX = sourceX;
            sourceX = sharedX - 2 * ARROW_SIZE;
        }
    }
    graphics.useStyle(GUIDELINE);
    // Draw the line from the source anchor to the shared edge
    graphics.drawArrow(sourceX, y, sharedX, y);
    // Draw the line from the target to the horizontal shared edge
    int ty = centerY(targetBounds);
    if (textDirection.isLeftSegment(targetSegmentTypeX)) {
        int tx = targetBounds.x;
        int margin = targetMargins.left;
        if (margin == 0 || !type.relativeToMargin) {
            graphics.drawArrow(tx + 2 * ARROW_SIZE, ty, tx, ty);
        } else {
            graphics.drawArrow(tx, ty, tx - margin, ty);
        }
    } else {
        assert textDirection.isRightSegment(targetSegmentTypeX);
        int tx = x2(targetBounds);
        int margin = targetMargins.right;
        if (margin == 0 || !type.relativeToMargin) {
            graphics.drawArrow(tx - 2 * ARROW_SIZE, ty, tx, ty);
        } else {
            graphics.drawArrow(tx, ty, tx + margin, ty);
        }
    }
}
Also used : SegmentType(com.android.tools.idea.uibuilder.model.SegmentType) Insets(com.android.tools.idea.uibuilder.model.Insets)

Aggregations

SegmentType (com.android.tools.idea.uibuilder.model.SegmentType)4 Insets (com.android.tools.idea.uibuilder.model.Insets)2