I am creating a custom view which is a kind of arc slider progress view.I can draw more or less of the arc based on where the user touches(on the x axis) by calculating the sweep, i do this by first calculating the percetage where the user touched along the x axis..0% would be all the way to the left and 100% would be all the way to the right.
I want to take this a step further, instead off drawing the arc based on the x coordinate that the user presses, I want to make it move only when the user touches on the actual arc draw path, so its more realistic. I am still new to custom views and my maths is limited but if I get some tips I would be grateful thanks
class ArcProgress extends View {
Context cx;
float width;
float height;
float center_x, center_y;
final RectF oval = new RectF();
final RectF touchArea = new RectF();
float sweep = 0;
float left, right;
int percent = 0;
public ArcProgress(Context context) {
super(context);
cx = context;
}
public int getPercentage() {
return percent;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setBackgroundColor(0xfff0ebde);
width = (float) getWidth();
height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 3;
} else {
radius = width / 3;
}
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0xffd2c8b6);
paint.setStrokeWidth(35);
paint.setStyle(Paint.Style.STROKE);
center_x = width / 2;
center_y = height / 2;
left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;
oval.set(left, top, right, bottom);
//this is the background arc, it remains constant
canvas.drawArc(oval, 180, 180, false, paint);
paint.setStrokeWidth(10);
paint.setColor(0xffe0524d);
//this is the red arc whichhas its sweep argument manipulated by on touch
canvas.drawArc(oval, 180, sweep, false, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float xPosition = event.getX();
float yPosition = event.getY();
if (oval.contains(xPosition, yPosition)) {
float x = xPosition - left;
float s = x * 100;
float b = s / oval.width();
percent = Math.round(b);
sweep = (180 / 100.0f) * (float) percent;
invalidate();
} else {
if (xPosition < left) {
percent = 0;
sweep = (180 / 100.0f) * (float) percent;
invalidate();
}
if (xPosition > right) {
percent = 100;
sweep = (180 / 100.0f) * (float) percent;
invalidate();
}
}
}
return true;
}
}
I want to make it move only when the user touches on the actual arc
draw path
At the beginning of onTouchEvent() you need to check whether xPosition and yPosition are fulfilling some condition. If yes, you do the stuff, which you are doing now. If no, return true.
Condition:
We want to check whether x, y are in that grey arc background:
Let's calculate a distance from (x, y) to that point (a, b) in the center:
final dist = distance(x, y, a, b)
distance() is a simple Euclidean distance between points (x,y) and (a,b):
double distance(int x, int y, int a, int b)
{
return Math.sqrt((x - a) * (x - a) + (y - b) * (y - b));
}
x, y are in that grey arc background, if y > Y && dist >= r && dist <= R.
Does this work for you?
You don't need a lot of Maths. You can calculate the distance of the touch point from the center of your arc (it's a circle so it's easy) and the compare that with the radius you are using. That will tell you if the point is on the arc (almost, see below for full case).
Point touchEv = ...;
Point circleCenter = ...;
//the radius of the circle you used to draw the arc
float circleRadius = ...;
//how far from the arc should a touch point treated as it's on the arc
float maxDiff = getResources().getDimension(R.dimen.max_diff_dp);
//calculate the distance of the touch point from the center of your circle
float dist = Math.pow(touchEv.x-circleCenter.x,2) + Math.pow(touchEv.y- circleCenter.y,2)
dist = Math.sqrt(dist);
//We also need the bounding rect of the top half of the circle (the visible arc)
Rect topBoundingRect = new Rect(circleCenter.x - circleRadius,
circleCenter.y - circleRadius,
circleCenter.x + circleRadius,
circleCenter.y);
if (Math.abs(dist - circleRadius) <= maxDiff &&
topBoundingRect.contains(touchEv.x, touchEv.y)) {
// the user is touching the arc
}
Related
I am unable to create several instances of the waveClock object even though I have put it in an array and marked the centre positions for each object. I would like to create 4 objects in one window, all responding to different sound frequencies/beat onsets etc
Could someone shed some light on how to go about this? I believe it may be an issue with the centerX and centerY variables in the waveClock class
ArrayList<waveClock> waveClocks = new ArrayList<waveClock>();
//global variables
float angnoise, radiusnoise;
float xnoise, ynoise;
float angle = -PI/6;
float radius;
float strokeCol = 254;
int strokeChange = -1;
int speed; //changes speed of visualisation once beat is detected?
void setup()
//for every waveClock we need 180 pixels width, then add 20 pixels for first gap
size(740, 650);
background(255);
//code is called
waveClocks.add(new waveClock(100, height/2, minRadius, bassColour, lowBassBand, highBassBand, numberOfLowOnsetsThreshold));
waveClocks.add(new waveClock(280, height/2, minRadius, midColour, lowMidBand, highMidBand, numberOfMidOnsetsThreshold));
waveClocks.add(new waveClock(460, height/2, minRadius, highColour, lowHighBand, highHighBand, numberOfHighOnsetsThreshold));
waveClocks.add(new waveClock(640, height/2, minRadius, veryHighColour, lowVeryHighBand, highVeryHighBand, numberOfVeryHighOnsetsThreshold));
//set the min and max radius of each of the viz circles
/* for (int i = 0; i < waveClocks.size(); i++) {
//go through the arraylist of waveClocks and set the min and max radius of each circle
waveClocks.get(i).setMinMaxRadius(minRadius, maxRadius);
}*/
song.play();
beat = new BeatDetect(song.bufferSize(), song.sampleRate());
bl = new BeatListener(beat, song);
}
void draw() {
//clear the screen by painting it black
//background(0);
for (int i = 0; i < waveClocks.size(); i++) {
//has there been a beat in the range? get(circle ID).low band, high band etc.
if (beat.isRange(waveClocks.get(i).getLowBand(), waveClocks.get(i).getHighBand(), waveClocks.get(i).getOnsetThreshold())) {
waveClocks.get(i).setMaxRadius();
}
//waveClocks.get(i).drawCircle();
waveClocks.get(i).drawWaveClock();
}
}
waveClock class in a separate tab
//class is an architecture blueprint
//objects are the actual buildings built from the methods (can make as many as you like)
//constructor is the builder/constructor literally
class waveClock {
float centerX; //co-ordinates of circle's position
float centerY; //co-ordinates of circle's position
float radius; //avg radius
// float minRadius; //smallest size it can be
// float maxRadius; //biggest size it can be
color col; //colour
int onsetThreshold; //
int lowBand; //looks at lowest band of frequency and makes circle sensitive to it
int highBand; //looks at highest band of frequency and makes circle sensitive to it
boolean onset; //has there been an onset (beat has occurred or not?)
//the constructor
waveClock(float x, float y, float r, color c, int lb, int hb, int t) {
centerX = x;
centerY = y;
radius = r;
col = c;
lowBand = lb;
highBand = hb;
onsetThreshold = t;
}
void drawWaveClock() {
radiusnoise += 0.005;
radius = (noise(radiusnoise)*350) + 1;
angnoise += 0.005;
angle += (noise(angnoise)*6) - 3;
if (angle > 360) {
angle -= 360;
} else if (angle < 0) {
angle += 360;
}
xnoise += 0.01;
ynoise =+ 0.01;
float centerX = width/2 + (noise(xnoise)*100) - 50;
float centerY = height/2 + (noise(ynoise)*100) - 50;
float rad = radians(angle);
float x1 = centerX + (radius*cos(rad));
float y1 = centerY + (radius*sin(rad));
float opprad = rad + PI;
float x2 = centerX + (radius*cos(opprad));
float y2 = centerY + (radius*sin(opprad));
strokeCol += strokeChange;
if (strokeCol > 354) {
strokeChange = -1;
} else if (strokeCol < 0) {
strokeChange = 1;
}
stroke(strokeCol, 60);
strokeWeight(1);
line(x1, y1, x2, y2);
}
}
You aren't ever using the class-level centerX and centerY variables. Instead, you're recalculating a new centerX and centerY in the drawWaveClock() function.
float centerX = width/2 + (noise(xnoise)*100) - 50;
float centerY = height/2 + (noise(ynoise)*100) - 50;
These are all drawn from the center of the screen, so the waves will end up in the same position.
In the future, please try to narrow your problem down to a MCVE that demonstrates the problem. Also please use proper naming conventions- classes start with an upper-case letter, for example. Good luck.
I am working in a existing project for an amazfit watchface. Code is based in java. The question is: In original project, for show battery, steps and sport percentage, show three circles. My idea is to draw a rectangle (or a line) instead the original circle. The problem is I am new programming in java and I donĀ“t know for change this without FC app.
this watch has two screens: one active and other in stand-by mode (8colors only)
active mode draws circle, standby mode works with an png image.
This is the code (for circles):
package es.xxxx.xxxx.widget;
private final float startAngleBattery = 30;
private final float arcSizeBattery = 360 - startAngleBattery - startAngleBattery;
#Override
public void init(Service service) {
this.thickness = (int) service.getResources().getDimension(R.dimen.xxxx_circles_thickness);
this.textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.textPaint.setTypeface(ResourceManager.getTypeFace(service.getResources(), ResourceManager.Font.BEBAS_NEUE));
this.textPaint.setTextSize(service.getResources().getDimension(R.dimen.xxxx_circles_font_size));
this.textPaint.setColor(service.getResources().getColor(R.color.xxxx_time_colour));
this.textPaint.setTextAlign(Paint.Align.CENTER);
this.ring = new Paint(Paint.ANTI_ALIAS_FLAG);
this.ring.setStrokeCap(Paint.Cap.ROUND);
this.ring.setStyle(Paint.Style.STROKE);
this.ring.setStrokeWidth(this.thickness);
this.circle = new Paint(Paint.ANTI_ALIAS_FLAG);
this.circle.setColor(Color.BLACK);
this.circle.setStrokeWidth(1f);
this.circle.setStyle(Paint.Style.STROKE);
#Override
public void draw(Canvas canvas, float width, float height, float centerX, float centerY) {
int count = canvas.save();
int radius = Math.round(Math.min(width / 2, height / 2)) - this.thickness;
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
// rotate from 0 to 270 degrees
canvas.rotate(90, centerX, centerY);
this.ring.setColor(this.backgroundColour);
canvas.drawArc(oval, startAngleBattery, arcSizeBattery, false, ring);
if (batterySweepAngle != null) {
float px = getPointX(oval, centerX, startAngleBattery, batterySweepAngle);
float py = getPointY(oval, centerY, startAngleBattery, batterySweepAngle);
this.ring.setColor(this.batteryColour);
canvas.drawArc(oval, startAngleBattery, batterySweepAngle, false, ring);
canvas.drawCircle(px, py, this.thickness / 3f, circle);
canvas.drawCircle(px, py, this.thickness / 6f, circle);
}
canvas.restoreToCount(count);
if (this.batteryData != null) {
String text = String.format("%02d", this.batteryData.getLevel() * 100 / this.batteryData.getScale());
canvas.drawText(text, batteryTextLeft, batteryTextTop, textPaint);
}
}
#Override
public void onDataUpdate(DataType type, Object value) {
switch (type) {
case BATTERY:
onBatteryData((Battery) value);
break;
}
}
#Override
public List<DataType> getDataTypes() {
return Arrays.asList(DataType.BATTERY);
private void onBatteryData(Battery battery) {
this.batteryData = battery;
if (batteryData == null) {
this.batterySweepAngle = 0f;
} else {
float scale = batteryData.getLevel() / (float) batteryData.getScale();
this.batterySweepAngle = Math.min(arcSizeBattery, arcSizeBattery * scale);
}
}
private RectF nextOval(RectF oval) {
oval.left = oval.left + this.thickness + MARGIN;
oval.top = oval.top + this.thickness + MARGIN;
oval.right = oval.right - this.thickness - MARGIN;
oval.bottom = oval.bottom - this.thickness - MARGIN;
return oval;
}
private float getPointX(RectF oval, float cx, float startAngle, float sweepAngle) {
float width = oval.right - oval.left;
return (float) (cx + (width / 2D) * Math.cos((sweepAngle + startAngle) * Math.PI / 180));
}
private float getPointY(RectF oval, float cy, float startAngle, float sweepAngle) {
float height = oval.bottom - oval.top;
return (float) (cy + (height / 2D) * Math.sin((sweepAngle + startAngle) * Math.PI / 180));
}
#Override
public List<SlptViewComponent> buildSlptViewComponent(Service service) {
Typeface timeTypeFace = ResourceManager.getTypeFace(service.getResources(), ResourceManager.Font.BEBAS_NEUE);
SlptLinearLayout power = new SlptLinearLayout();
power.alignX = 2;
power.alignY = 2;
power.add(new SlptPowerNumView());
power.setTextAttrForAll(
service.getResources().getDimension(R.dimen.xxxx_circles_font_size_slpt),
-1,
timeTypeFace
);
power.setStart(
(int) service.getResources().getDimension(R.dimen.xxxx_battery_text_left_slpt),
(int) service.getResources().getDimension(R.dimen.xxxx_battery_text_top_slpt));
SlptPowerArcAnglePicView powerArcView = new SlptPowerArcAnglePicView();
powerArcView.setImagePicture(Util.assetToBytes(service, "battery_splt.png"));
powerArcView.start_angle = (int) startAngleBattery + 180 - 3;
powerArcView.full_angle = (int) arcSizeBattery + 6;
return Arrays.asList(power, powerArcView);
}
}
Thanks in advance.
For anyone still searching...
You can draw the rectangular in the "draw" function that runs in loop constantly when screen is on, however, screen off (SLPT mode) uses ingenic's libraries to draw (function buildSlptViewComponent) and there is the real problem.
I don't want to get into details because it would be pages, so have a look at GreatFit project.
I am trying to implement a zoom to an isometric map using LWJGL. Currently I have the functions
public static void setCameraPosition(float x, float y) {
x *= zoom;
y *= zoom;
cameraX = x;
cameraY = y;
x -= (Display.getWidth() / 2) * zoom;
y -= (Display.getHeight() / 2) * zoom;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
GLU.gluLookAt(x, y, 1f, x, y, 0, 0.0f, 1.0f, 0.0f);
}
which sets the camera center to a point (x, y),
public static Point getMouseCoordinates() {
float x = Mouse.getX() * getZoom() + getCameraLeft();
float y = (Display.getHeight() - Mouse.getY()) * getZoom() + getCameraTop();
return new Point((int) x, (int) y);
}
which returns the current mouse coordinates, and
public static void setZoom(int newZoom) {
if (newZoom >= 4) newZoom = 4;
else if (newZoom <= 1) newZoom = 1;
if (zoom == newZoom) return;
float x = ?; <-----
float y = ?; <-----
zoom = newZoom;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 0+Display.getWidth() * zoom , 0+Display.getHeight() * zoom, 0, 1, -1);
setCameraPosition((int) x, (int) y);
}
which is supposed to set the zoom to an integer value between 1 and 4. As you can see, I would like to set the camera position after changing the zoom to a certain point - and that point needs to be calculated so that the current mouse position does not change (aka zooming in to the mouse position, which is for example what Google Maps does). I have been trying for a good 2 days now, I've tried so many things, but I just couldn't figure out the equation to calculate x and y.
Please note that all points returned and entered are relative to the position of the map, specifically to the top piece of the map (whose top corner point is (0, 0)). The values getCameraLeft() and getCameraTop() in the getMouseCoordinates() function return
public static float getCameraLeft() {
return cameraX - zoom * (Display.getWidth() / 2);
}
and
public static float getCameraTop() {
return cameraY - zoom * (Display.getHeight() / 2);
}
.
Any help would be appreciated. I'm hoping, I did not express myself too complicated.
I finally found the correct equation:
float x = getMouseCoordinates().getX() + (getCameraX() - getMouseCoordinates().getX()) * (float) newZoom / (float) zoom;
float y = getMouseCoordinates().getY() + (getCameraY() - getMouseCoordinates().getY()) * (float) newZoom / (float) zoom;
Thank you anyways, I'm sure eventually someone would have given me the correct answer :)
I'm new to animation in android. I'm working with a tutorial I have found on youtube. The app draws a picture of a ball on a canvas and then moves diagonally. I'd like to make the ball move in a circle. I've found some information about the basic math of circular motion but I'm having trouble implementing it. Could someone look at my code and tell me what I'm doing wrong. Thanks.
Here is my code:
public class DrawingTheBall extends View {
Bitmap bball;
int x,y;
public DrawingTheBall(Context context) {
super(context);
// TODO Auto-generated constructor stub
bball = BitmapFactory.decodeResource(getResources(), R.drawable.blueball);
x = 0;
y = 0;
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
Rect ourRect = new Rect();
ourRect.set(0, 0, canvas.getWidth(), canvas.getHeight()/2);
float a = 10;
float b = 10;
float r = 20;
double theta = 0;
theta = Math.toRadians(45);
Paint blue = new Paint();
blue.setColor(Color.BLUE);
blue.setStyle(Paint.Style.FILL);
canvas.drawRect(ourRect, blue);
if(x < canvas.getWidth()){
//x += 10;
x = (int) (a +r*Math.cos(theta));
}else{
x = 0;
}
if(y < canvas.getHeight()){
//y += 10;
y = (int) (b +r*Math.sin(theta));
}else{
y = 0;
}
Paint p = new Paint();
canvas.drawBitmap(bball, x, y, p);
invalidate();
}
}
Basically you need to use the Sine and Cosine trigonometric functions, which given the angle will give you the ratios of the corresponding x, and y coordinates on your screen.
something along:
double x = a + r * sin(angle);
double y = b + r * cos(angle);
should work.
where:
r - is the radius of the circle
(a,b) - is the center of the circle
angle - is the desired angle
Of course you need to increment the angle, in order for your object to move.
Mathematically a point on the circle is defined by an angle theta and a distance from the center radius. In your code the angle is a constant 100 so it never moves on the circle. What you want to do is increase the angle in your update.
theta = theta + Math.toRadians(10);
x = a + r*Math.cos(theta);
y = b + r*Math.sin(theta);
This will let you move on a circle that centers on (a,b) with radius r, 10 degrees at a time.
To your comment, add theta as a field and don't set it to 45 inside onDraw, if you want to start at 45 degrees you can initialize it to 45 inside your constructor.
int x,y;
to
int x,y, theta;
To your comment
int x,y, theta;
public DrawingTheBall(Context context) {
super(context);
bball = BitmapFactory.decodeResource(getResources(), R.drawable.blueball);
x = 0;
y = 0;
theta = 45;
}
And
public void onDraw(Canvas canvas){
super.onDraw(canvas);
Rect ourRect = new Rect();
ourRect.set(0, 0, canvas.getWidth(), canvas.getHeight()/2);
float a = 10;
float b = 10;
float r = 20;
// double theta = 0; //You are using a local variable that shadows the field, it starts at 0 everytime
// theta = Math.toRadians(45); //You are setting it to 45 degrees everytime, instead:
theta = theta + Math.toRadians(10); //Increase of 10 degrees
Paint blue = new Paint();
blue.setColor(Color.BLUE);
blue.setStyle(Paint.Style.FILL);
canvas.drawRect(ourRect, blue);
if(x < canvas.getWidth()){
//x += 10;
x = (int) (a +r*Math.cos(theta));
}else{
x = 0;
}
if(y < canvas.getHeight()){
//y += 10;
y = (int) (b +r*Math.sin(theta));
}else{
y = 0;
}
Paint p = new Paint();
canvas.drawBitmap(bball, x, y, p);
invalidate();
}
Take a look at this post from another SO ticket, seems very similar.
Android - Moving an object in circle
I'm trying to create an Illustrator style selection box for geometric objects in java.
When the object is selected a border is drawn and it's possible to drag the little rectangles to re-size the object. I'd also like to be able to rotate the box by dragging.
So far I can scale the box and I can rotate the box but I can't do the two together. Imagine the box is at an angle of 45 degrees. When you drag the corner to enlarge the box in the x direction this will increase both the width and height of the box because of the angle.
I can get it to work by using:
dx = dx*cos(theta) - dy*sin(theta);
dy = dy*cos(theta) + dx*sin(theta);
But this only works when the pivot point is in the top left corner. I want to be able to move the pivot around and then scale and rotate. This problem must have been solved lots of times before. Is there a way I can use an affine transform to convert my mouse draw to the coordinate space of the rotated object? I'd prefer not to have to dig through the trigonometry! Thanks in advance.
You are in luck - Java2D provides an AffineTransform class that should do everything you are looking for.
It can handle rotations, scaling, shears, flips, translations etc.
There is a concatenate function that should enable you to combine multiple transforms (as moonwave99 points out you need to do them in the right order as the combination of affine transformations is not commutative)
I pretty much worked the answer out myself although it's still not possible to move the pivot point around. In case it's helpful here's the full code for a working example using JavaFX 2.2. You can scale and rotate the box by dragging the corners around:
public class SelectionBoxDemo extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage arg0) throws Exception {
Stage stage = new Stage ();
// Root is the base pane in which we put everything
Pane root = new Pane ();
SelectionBox sb = new SelectionBox ();
sb.setSize(100, 100);
root.getChildren().add(sb);
// Create a new scene
Scene scene = new Scene (root);
stage.setScene(scene);
stage.setMinHeight(500);
stage.setMinWidth(500);
stage.show();
}
public static class SelectionBox extends Region {
private enum Position {
TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left;
}
// Create the corners
private Rectangle tr, tl, br, bl;
// Create selection lines
final private Line top, right, bottom, left;
// Size of corner boxes
private double cornerSize = 10;
// Create a new rotate transform
private final Rotate rotate = new Rotate();
{
getTransforms().add(rotate);
rotate.setPivotX(cornerSize);
rotate.setPivotY(cornerSize);
}
// Circle which is dragged to rotate the box
private final Circle rotateCircle;
// Variables to store mouse x and y
private double x, y;
public SelectionBox () {
// Create the circle which can be dragged to rotate the box
rotateCircle = new Circle(5);
rotateCircle.setFill(Color.PINK);
rotateCircle.setStroke(Color.rgb(0,0,0, 0.75));
// Make it draggable
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
setMouse(event.getSceneX(), event.getSceneY());
}
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
// Used to get the scene position of the corner of the box
Transform localToScene = getLocalToSceneTransform();
double x1 = getMouseX();
double y1 = getMouseY();
double x2 = event.getSceneX();
double y2 = event.getSceneY();
double px = rotate.getPivotX() + localToScene.getTx();
double py = rotate.getPivotY() + localToScene.getTy();
// Work out the angle rotated
double th1 = clockAngle(x1, y1, px, py);
double th2 = clockAngle(x2, y2, px, py);
double angle = rotate.getAngle();
angle += th2 - th1;
// Rotate the rectangle
rotate.setAngle(angle);
setMouse(event.getSceneX(), event.getSceneY());
}
});
// Build the corners
tr = buildCorner (0,0, Position.TopRight);
tl = buildCorner (0,0, Position.TopLeft);
br = buildCorner (0,0, Position.BottomRight);
bl = buildCorner (0,0, Position.BottomLeft);
// Build the lines
top = buildLine(0, 100, -100, 0);
bottom = buildLine(0, 0, 0, 0);
left = buildLine(0, 0, 0, 0);
right = buildLine(0, 0, 0, 0);
getChildren().addAll(top, bottom, left, right, tr, tl, br, bl, rotateCircle);
}
// Return the angle from 0 - 360 degrees
public double clockAngle (double x, double y, double px, double py) {
double dx = x - px;
double dy = y - py;
double angle = Math.abs(Math.toDegrees(Math.atan2(dy, dx)));
if(dy < 0) {
angle = 360 - angle;
}
return angle;
}
// Set the size of the selection box
public void setSize (double width, double height) {
tl.setX(0);
tl.setY(0);
tr.setX(width + cornerSize);
tr.setY(0);
bl.setX(0);
bl.setY(height + cornerSize);
br.setX(width + cornerSize);
br.setY(height + cornerSize);
setLine(top, cornerSize, cornerSize, width + cornerSize, cornerSize);
setLine(bottom, cornerSize, height + cornerSize, width + cornerSize, height + cornerSize);
setLine(right, width + cornerSize, cornerSize, width + cornerSize, height + cornerSize);
setLine(left, cornerSize, cornerSize, cornerSize, height + cornerSize);
top.setCursor(Cursor.V_RESIZE);
bottom.setCursor(Cursor.V_RESIZE);
left.setCursor(Cursor.H_RESIZE);
right.setCursor(Cursor.H_RESIZE);
tr.setCursor(Cursor.CROSSHAIR);
tl.setCursor(Cursor.CROSSHAIR);
br.setCursor(Cursor.CROSSHAIR);
bl.setCursor(Cursor.CROSSHAIR);
rotateCircle.setTranslateX(width + 2 * cornerSize + rotateCircle.getRadius());
rotateCircle.setTranslateY(height + 2 * cornerSize + rotateCircle.getRadius());
}
// Set the start and end points of a line
private void setLine (Line l, double x1, double y1, double x2, double y2) {
l.setStartX(x1);
l.setStartY(y1);
l.setEndX(x2);
l.setEndY(y2);
}
// Save mouse coordinates
private void setMouse(double x, double y) {
this.x = x;
this.y = y;
}
private double getMouseX () {
return x;
}
private double getMouseY () {
return y;
}
// Selection box width
public double w () {
return Math.abs(bottom.getEndX() - bottom.getStartX());
}
// Selection box height
public double h () {
return Math.abs(right.getEndY() - right.getStartY());
}
// Build a corner of the rectangle
private Rectangle buildCorner (double x, double y, final Position pos) {
// Create the rectangle
Rectangle r = new Rectangle();
r.setX(x);
r.setY(y);
r.setWidth(cornerSize);
r.setHeight(cornerSize);
r.setStroke(Color.rgb(0,0,0,0.75));
r.setFill(Color.rgb(0, 0, 0, 0.25));
r.setStrokeWidth(1);
r.setStrokeType(StrokeType.INSIDE);
// Make it draggable
r.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
setMouse(event.getSceneX(), event.getSceneY());
}
});
r.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
// Get the mouse deltas
double dx = event.getSceneX() - getMouseX();
double dy = event.getSceneY() - getMouseY();
// Set save the current mouse value
setMouse(event.getSceneX(), event.getSceneY());
// Get the rotation angle in radians
double tau = - Math.toRadians(rotate.getAngle());
// Create variables for the sin and cosine
double sinTau = Math.sin(tau);
double cosTau = Math.cos(tau);
// Perform a rotation on dx and dy to the object co-ordinate frame
double dx_ = dx * cosTau - dy * sinTau;
double dy_ = dy * cosTau + dx * sinTau;
// Create a variable for the change in height of the box
double dh = h();
// Work out the new positions for the resize corners
if(pos == Position.TopLeft) {
// Set the size based on the transformed dx and dy values
setSize(w() - dx_, h() - dy_);
// Move the shape
setTranslateX(getTranslateX() + dx);
setTranslateY(getTranslateY() + dy);
}
else if (pos == Position.TopRight) {
// This comes down to geometry - you need to know the
// amount the height of the shape has increased
setSize(w() + dx_ , h() - dy_);
// Work out the delta height - that is then used to work out
// the correct translations
dh = h() - dh;
setTranslateX (getTranslateX() - dh * sinTau);
setTranslateY (getTranslateY() - dh * cosTau);
}
else if (pos == Position.BottomRight) {
setSize(w() + dx_ , h() + dy_ );
}
else if (pos == Position.BottomLeft) {
setSize(w() - dx_, h() + dy_);
dh = h() - dh;
setTranslateX(getTranslateX() + dx - dh * sinTau );
setTranslateY(getTranslateY() + dy - dh * cosTau);
}
}
});
return r;
}
private Line buildLine (double x1, double y1, double x2, double y2) {
Line l = new Line (x1, y1, x2, y2);
l.setStroke(Color.rgb(0, 0, 0, 0.75));
l.setStrokeWidth (0.5);
return l;
}
}
}