blob: 584b59322acb67792c77f4405cfad3b1471bfa28 [file] [log] [blame]
/*
* Copyright (C) 2015 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.uibuilder.model;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import org.intellij.lang.annotations.MagicConstant;
import java.awt.*;
/**
* A selection handle is a small rectangle on the border of a selected view which lets you
* change the size of the view by dragging it.
*/
public class SelectionHandle {
/**
* Size of the selection handle radius, in control coordinates. Note that this isn't
* necessarily a <b>circular</b> radius; in the case of a rectangular handle, the
* w and the h are both equal to this radius.
* Note also that this radius is in <b>control</b> coordinates, whereas the rest
* of the class operates in layout coordinates. This is because we do not want the
* selection handles to grow or shrink along with the screen zoom; they are always
* at the given pixel size in the control.
*/
public final static int PIXEL_RADIUS = 8;
/**
* Extra number of pixels to look beyond the actual radius of the selection handle
* when matching mouse positions to handles
*/
public final static int PIXEL_MARGIN = 2;
/**
* The position of the handle in the selection rectangle
*/
public enum Position {
TOP_MIDDLE(Cursor.N_RESIZE_CURSOR, 0.5, 0),
TOP_RIGHT(Cursor.NE_RESIZE_CURSOR, 1, 0),
RIGHT_MIDDLE(Cursor.E_RESIZE_CURSOR, 1, 0.5),
BOTTOM_RIGHT(Cursor.SE_RESIZE_CURSOR, 1, 1),
BOTTOM_MIDDLE(Cursor.S_RESIZE_CURSOR, 0.5, 1),
BOTTOM_LEFT(Cursor.SW_RESIZE_CURSOR, 0, 1),
LEFT_MIDDLE(Cursor.W_RESIZE_CURSOR, 0, 0.5),
TOP_LEFT(Cursor.NW_RESIZE_CURSOR, 0, 0);
/** Factor to multiply component width to obtain handle x location */
public final double alignX;
/** Factor to multiply component height with to obtain handle y location */
public final double alignY;
/** Predefined AWT cursor constant */
private final int myAwtCursor;
Position(@MagicConstant(valuesFromClass = Cursor.class) int awtCursor, double alignX, double alignY) {
myAwtCursor = awtCursor;
this.alignX = alignX;
this.alignY = alignY;
}
@MagicConstant(valuesFromClass = Cursor.class)
private int getCursorType() {
return myAwtCursor;
}
/**
* Is the {@link SelectionHandle} somewhere on the left edge?
*/
public boolean isLeft() {
return this == TOP_LEFT || this == LEFT_MIDDLE || this == BOTTOM_LEFT;
}
/**
* Is the {@link SelectionHandle} somewhere on the right edge?
*/
public boolean isRight() {
return this == TOP_RIGHT || this == RIGHT_MIDDLE || this == BOTTOM_RIGHT;
}
/**
* Is the {@link SelectionHandle} somewhere on the top edge?
*/
public boolean isTop() {
return this == TOP_LEFT || this == TOP_MIDDLE || this == TOP_RIGHT;
}
/**
* Is the {@link SelectionHandle} somewhere on the bottom edge?
*/
public boolean isBottom() {
return this == BOTTOM_LEFT || this == BOTTOM_MIDDLE || this == BOTTOM_RIGHT;
}
}
/** The associated component */
@NonNull
public final NlComponent component;
/**
* The x coordinate of the center of the selection handle
*/
@AndroidCoordinate
public int getCenterX() {
return component.x + (int)(myPosition.alignX * component.w);
}
/**
* The y coordinate of the center of the selection handle
*/
@AndroidCoordinate
public int getCenterY() {
return component.y + (int)(myPosition.alignY * component.h);
}
/**
* The position of the handle in the selection rectangle
*/
private final Position myPosition;
/**
* Constructs a new {@link SelectionHandle} at the given layout coordinate
* corresponding to a handle at the given {@link Position}.
*
* @param component the associated component
* @param position the position of the handle in the selection rectangle
*/
public SelectionHandle(@NonNull NlComponent component,
@NonNull Position position) {
this.component = component;
myPosition = position;
}
/**
* Determines whether the given point is within the given distance in
* Android coordinates. The distance should incorporate at least the equivalent
* distance to the control coordinate space {@link #PIXEL_RADIUS}, but usually with a
* few extra pixels added in to make the corners easier to target.
*
* @param x the mouse x position in Android coordinates
* @param y the mouse y position in Android coordinates
* @param distance the distance from the center of the handle to check whether the
* point fits within
* @return true if the given point is within the given distance of this handle
*/
public boolean contains(@AndroidCoordinate int x, @AndroidCoordinate int y, @AndroidCoordinate int distance) {
int xDelta = Math.abs(x - getCenterX());
if (xDelta > distance) {
return false;
}
int yDelta = Math.abs(y - getCenterY());
return yDelta <= distance;
}
/**
* Returns the position of the handle in the selection rectangle
*
* @return the position of the handle in the selection rectangle
*/
public Position getPosition() {
return myPosition;
}
/**
* Returns the AWT cursor type to use for this selection handle
*
* @return the position of the handle in the selection rectangle
*/
@MagicConstant(valuesFromClass = Cursor.class)
public int getAwtCursorType() {
return myPosition.getCursorType();
}
/**
* Returns the cursor to use for this selection handle
*
* @return the cursor to use for this selection handle
*/
@NonNull
public Cursor getCursor() {
return Cursor.getPredefinedCursor(getAwtCursorType());
}
/** Returns the horizontal edge (top or bottom) this selection handle is associated with, if any */
@Nullable
public SegmentType getHorizontalEdge() {
return myPosition.isTop() ? SegmentType.TOP : myPosition.isBottom() ? SegmentType.BOTTOM : null;
}
/** Returns the vertical edge (left or right) this selection handle is associated with, if any */
@Nullable
public SegmentType getVerticalEdge() {
return myPosition.isLeft() ? SegmentType.LEFT : myPosition.isRight() ? SegmentType.RIGHT : null;
}
}