1.完成透明Canvas的封装,画笔功能完成

2.完成矩形和椭圆的统一调整大小
1.优化代码结构
This commit is contained in:
yang 2022-09-19 14:25:47 +08:00
parent f0cc080a5e
commit a6cbd4b341
11 changed files with 814 additions and 420 deletions

View File

@ -0,0 +1,41 @@
package org.rococy.roomit.control;
import javafx.scene.Group;
import javafx.scene.Node;
/**
* 存放canvas的容器
*
* @author Rococy
* @date 2022/9/17
*/
public class CanvasGroup extends Group {
/**
* canvas配置
*/
private static final int TRANSPARENT_CANVAS_COUNT = 5;
private static boolean isTransparent = true;
public CanvasGroup() {
initCanvas();
}
private void initCanvas() {
if (isTransparent) {
// 初始化层叠Canvas
for (int i = 0; i < TRANSPARENT_CANVAS_COUNT; i++) {
RoomItCanvas canvas = new RoomItCanvas(isTransparent);
canvas.setViewOrder(i);
this.addChildren(canvas);
}
} else {
// 直接给一个Canvas即可
this.addChildren(new RoomItCanvas(isTransparent));
}
}
public void addChildren(Node node) {
this.getChildren().add(node);
}
}

View File

@ -0,0 +1,152 @@
package org.rococy.roomit.control;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Ellipse;
import org.rococy.roomit.control.base.Resizer;
/**
* @author Rococy
* @date 2022/9/18
*/
public class ResizableEllipseWrapper extends Resizer {
/**
* 配置项
*/
private static final int BORDER_WIDTH = 2;
private static final String BORDER_COLOR = "linear-gradient(to bottom, #f64f5988, #c471ed88, #12c2e988);";
/**
* 内部椭圆
*/
private final InnerEllipse innerEllipse = new InnerEllipse();
/**
* 与内部椭圆的间隔距离
*/
private static final int ELLIPSE_INTERVALS = 3;
/**
* @param originX 在整个屏幕内的初始x值
* @param originY 在整个屏幕内的初始y值
*/
public ResizableEllipseWrapper(double originX, double originY) {
super(originX, originY);
// 设置样式
this.setStyle(String.format("-fx-border-width: %spx; -fx-border-style:solid; -fx-border-color: %s; -fx-border-radius: %s;", BORDER_WIDTH, BORDER_COLOR, 4));
// 添加内椭圆
this.addChildren(innerEllipse);
// 初始化事件
initEvents();
}
@Override
protected double getBorderWidth() {
return BORDER_WIDTH;
}
/**
* 获取内部椭圆
*
* @return 椭圆
*/
public Ellipse getInnerEllipse() {
return innerEllipse;
}
/**
* 获取矩形与内部椭圆的间隔距离
*
* @return 间隔距离
*/
public int getEllipseIntervals() {
return BORDER_WIDTH + ELLIPSE_INTERVALS;
}
/**
* 添加子元素
*
* @param node Node元素
*/
public void addChildren(Node node) {
this.getChildren().add(node);
}
/**
* 设置样式
*/
private void setBorderWidth(int borderWidth) {
super.setStyle(String.format("-fx-border-width: %spx; -fx-border-style:solid; -fx-border-color: %s; -fx-border-radius: %s;", borderWidth, BORDER_COLOR, 4));
}
private void showBorder() {
this.setBorderWidth(BORDER_WIDTH);
}
/**
* 初始化事件
*/
private void initEvents() {
// 鼠标进入
bindMouseEnterEvent();
// 鼠标退出
bindMouseExistedEvent();
// 拓展鼠标拖拽事件
bindExtraMouseDraggedEvent();
}
/**
* 鼠标进入事件鼠标进入显示边框线
*/
private void bindMouseEnterEvent() {
this.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> showBorder());
}
/**
* 鼠标退出事件鼠标进入隐藏边框线
*/
private void bindMouseExistedEvent() {
this.addEventHandler(MouseEvent.MOUSE_EXITED, e -> this.setBorderWidth(0));
}
/**
* 拓展鼠标拖拽事件
*/
private void bindExtraMouseDraggedEvent() {
this.addEventHandler(MouseEvent.MOUSE_DRAGGED, e -> {
// 拖拽也要显示border
this.showBorder();
double centerX = this.getPrefWidth() / 2;
double centerY = this.getPrefHeight() / 2;
innerEllipse.setCenterX(centerX);
innerEllipse.setCenterY(centerY);
innerEllipse.setRadiusX(centerX - this.getEllipseIntervals());
innerEllipse.setRadiusY(centerY - this.getEllipseIntervals());
});
}
/**
* 矩形内部的椭圆
*/
private static class InnerEllipse extends Ellipse {
/**
* 配置项
*/
private static final String FILL_COLOR = "#00000000";
private static final String STROKE_COLOR = "linear-gradient(to bottom, #f64f59, #c471ed, #12c2e9);";
private static final int BORDER_WIDTH = 3;
public InnerEllipse() {
this.setStyle(String.format("-fx-fill: %s; -fx-stroke: %s; -fx-stroke-width: %d", FILL_COLOR, STROKE_COLOR, BORDER_WIDTH));
}
}
}

View File

@ -1,239 +1,43 @@
package org.rococy.roomit.control;
import javafx.scene.Cursor;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import org.rococy.roomit.enums.RectangleCtrl;
import org.rococy.roomit.control.base.Resizer;
/**
* 能改变尺寸的矩形
*
* @author Rococy
* @date 2022/9/14
* @date 2022/9/19
*/
public class ResizableRectangle extends Rectangle {
/**
* 矩形原点
*/
private double originX, originY;
/**
* 记录调整完矩形后的大小
*/
private double width, height;
/**
* 当处于移动状态时记录的xy值
*/
private double moveX, moveY;
/**
* 矩形操作类型
*/
private RectangleCtrl rectangleCtrl;
public class ResizableRectangle extends Resizer {
/**
* 配置项
*/
private static final String COLOR = "#f00";
private static final String FILL_COLOR = "#00000000";
private static final String STROKE_COLOR = "linear-gradient(to bottom, #f64f59, #c471ed, #12c2e9)";
private static final double BORDER_WIDTH = 3;
private static final String BORDER_RADIUS = "5px";
/**
* @param originX 在整个屏幕内的x值
* @param originY 在整个屏幕内的y值
* 最小宽高
*/
private static final double MIN_WIDTH = 8, MIN_HEIGHT = 8;
/**
* @param originX 在整个屏幕内的初始x值
* @param originY 在整个屏幕内的初始y值
*/
public ResizableRectangle(double originX, double originY) {
// 记录原点
this.originX = originX;
this.originY = originY;
super(originX, originY);
// 填充透明色边框颜色边框宽度
this.setFill(Paint.valueOf("#00000000"));
this.setStroke(Paint.valueOf(COLOR));
this.setStrokeWidth(BORDER_WIDTH);
// 初始化调整大小事件
this.initEvents();
// 填充颜色边框颜色边框宽度圆角
this.setStyle(String.format("-fx-fill: %s; -fx-border-color: %s; -fx-border-width: %spx; -fx-border-style:solid; -fx-border-radius: %s;", FILL_COLOR, STROKE_COLOR, BORDER_WIDTH, BORDER_RADIUS));
this.setMinSize(MIN_WIDTH, MIN_HEIGHT);
}
/**
* 初始化矩形调整大小事件
*/
public void initEvents() {
bindMousePressedEvent();
bindMouseMovedEvent();
bindMouseDraggedEvent();
bindMouseReleasedEvent();
@Override
protected double getBorderWidth() {
return BORDER_WIDTH;
}
/**
* 鼠标按下
*/
private void bindMousePressedEvent() {
this.setOnMousePressed(e -> {
width = this.getWidth();
height = this.getHeight();
if (rectangleCtrl == RectangleCtrl.MOVE) {
moveX = e.getScreenX() - this.getLayoutX();
moveY = e.getScreenY() - this.getLayoutY();
}
});
}
/**
* 鼠标移动
*/
private void bindMouseMovedEvent() {
this.setOnMouseMoved(e -> {
double width = this.getWidth(),
height = this.getHeight(),
x = e.getScreenX() - this.getLayoutX(),
y = e.getScreenY() - this.getLayoutY();
// topLeft
if (x <= 3 && y <= 3) {
this.setCursor(Cursor.SE_RESIZE);
rectangleCtrl = RectangleCtrl.TOP_LEFT;
return;
}
// topMiddle
if (x <= width - 3 && y <= 3) {
this.setCursor(Cursor.N_RESIZE);
rectangleCtrl = RectangleCtrl.TOP_MIDDLE;
return;
}
// topRight
if (x <= width && y <= 3) {
this.setCursor(Cursor.NE_RESIZE);
rectangleCtrl = RectangleCtrl.TOP_RIGHT;
return;
}
// centerLeft
if (x <= 3 && y <= height - 3) {
this.setCursor(Cursor.W_RESIZE);
rectangleCtrl = RectangleCtrl.CENTER_LEFT;
return;
}
// bottomLeft
if (x <= 3 && y <= height) {
this.setCursor(Cursor.NE_RESIZE);
rectangleCtrl = RectangleCtrl.BOTTOM_LEFT;
return;
}
// bottomMiddle
if (x <= width - 3 && y >= height - 3) {
this.setCursor(Cursor.N_RESIZE);
rectangleCtrl = RectangleCtrl.BOTTOM_MIDDLE;
return;
}
// bottomRight
if (x <= width && y >= height - 3) {
this.setCursor(Cursor.SE_RESIZE);
rectangleCtrl = RectangleCtrl.BOTTOM_RIGHT;
return;
}
// centerRight
if (x >= width - 3 && y <= height) {
this.setCursor(Cursor.W_RESIZE);
rectangleCtrl = RectangleCtrl.CENTER_RIGHT;
return;
}
// 移动指针
if (x >= 3 + 1 && x <= width - 3 && y >= 3 + 1 && y <= height - 3) {
this.setCursor(Cursor.MOVE);
rectangleCtrl = RectangleCtrl.MOVE;
return;
}
this.setCursor(Cursor.DEFAULT);
});
}
/**
* 鼠标拖拽
*/
private void bindMouseDraggedEvent() {
this.setOnMouseDragged(e -> {
// 防止快速操作
if (rectangleCtrl == null) {
return;
}
double screenX = e.getScreenX(),
screenY = e.getScreenY(),
x = screenX - originX,
y = screenY - originY;
switch (rectangleCtrl) {
case TOP_LEFT -> {
this.setLayoutX(screenX);
this.setLayoutY(screenY);
this.setWidth(width - x);
this.setHeight(height - y);
}
case TOP_MIDDLE -> {
this.setLayoutY(screenY);
this.setHeight(height - y);
}
case TOP_RIGHT -> {
this.setLayoutY(screenY);
this.setWidth(x);
this.setHeight(height - y);
}
case CENTER_LEFT -> {
this.setLayoutX(screenX);
this.setWidth(width - x);
}
case CENTER_RIGHT -> this.setWidth(x);
case BOTTOM_LEFT -> {
this.setLayoutX(screenX);
this.setWidth(width - x);
this.setHeight(y);
}
case BOTTOM_MIDDLE -> this.setHeight(y);
case BOTTOM_RIGHT -> {
this.setWidth(x);
this.setHeight(y);
}
case MOVE -> {
this.setLayoutX(screenX - moveX);
this.setLayoutY(screenY - moveY);
}
}
});
}
/**
* 鼠标抬起
*/
private void bindMouseReleasedEvent() {
this.setOnMouseReleased(e -> {
this.originX = this.getLayoutX();
this.originY = this.getLayoutY();
});
}
}

View File

@ -0,0 +1,182 @@
package org.rococy.roomit.control;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Paint;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import org.rococy.roomit.util.ScreenUtils;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* 自动透明的Canvas
*
* @author Rococy
* @date 2022/9/13
*/
public class RoomItCanvas extends Canvas {
/**
* 是否透明
*/
private final boolean isTransparent;
/**
* 透明度初始值为1
*/
private double opacity = 1;
/**
* 是否可用是否可以切换到顶层
*/
private boolean used = true;
/**
* 画笔
*/
private GraphicsContext gc;
/**
* 记录画笔开始画的位置
*/
private double graphicStartX, graphicStartY;
public RoomItCanvas(boolean isTransparent) {
// 初始化宽高
super(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight());
this.isTransparent = isTransparent;
// 初始化画笔
initGraphics();
// 初始化事件
initEvents();
}
/**
* 开始慢慢变透明
*/
public void startTransparency() {
used = false;
new Timer().schedule(new OpacityTask(), 0, 100);
}
/**
* 返回是否可用透明度是否已经从0归1
*
* @return 是否可用
*/
public boolean isUsed() {
return used;
}
/**
* 初始化画笔
*/
private void initGraphics() {
gc = this.getGraphicsContext2D();
// 画笔颜色
gc.setStroke(Paint.valueOf("#f00"));
// 让线变得圆滑
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
// 线条宽度
gc.setLineWidth(5);
}
/**
* 初始化事件
*/
private void initEvents() {
this.bindMousePressedEvent();
this.bindMouseDraggedEvent();
if (isTransparent) {
this.bindMouseReleasedEvent();
}
}
/**
* 鼠标按下
*/
private void bindMousePressedEvent() {
this.setOnMousePressed(e -> {
graphicStartX = e.getScreenX();
graphicStartY = e.getScreenY();
});
}
/**
* 鼠标拖拽
*/
private void bindMouseDraggedEvent() {
// 鼠标按下并拖动
this.setOnMouseDragged(e -> {
double endX = e.getX();
double endY = e.getY();
// 画线
gc.strokeLine(graphicStartX, graphicStartY, endX, endY);
// 连续画线
graphicStartX = endX;
graphicStartY = endY;
});
}
/**
* 鼠标抬起
*/
private void bindMouseReleasedEvent() {
this.setOnMouseReleased(e -> {
// 当前画完的Canvas开始变透明
this.startTransparency();
// 获取相邻的TransparentCanvas
CanvasGroup group = (CanvasGroup) getParent();
List<Node> nodeList = group.getChildren();
for (Node node : nodeList) {
// 即将置于顶层的canvas
RoomItCanvas topCanvas = (RoomItCanvas) node;
// 正在隐藏当中
if (!topCanvas.isUsed() || topCanvas == this) {
continue;
}
// 交换图形层叠位置
this.setViewOrder(topCanvas.getViewOrder());
topCanvas.setViewOrder(0);
break;
}
});
}
/**
* 透明定时线程
*/
private class OpacityTask extends TimerTask {
private final RoomItCanvas canvas = RoomItCanvas.this;
private static final double MIN_OPACITY = 0.1;
@Override
public void run() {
opacity *= 0.9;
canvas.setOpacity(opacity);
if (opacity < MIN_OPACITY) {
opacity = 1;
canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
canvas.setOpacity(opacity);
used = true;
this.cancel();
}
}
}
}

View File

@ -0,0 +1,22 @@
package org.rococy.roomit.control;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import org.rococy.roomit.util.ScreenUtils;
/**
* 操作画矩形圆形的面板
*
* @author Rococy
* @date 2022/9/17
*/
public class ShapePane extends Pane {
public ShapePane() {
this.setPrefSize(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight());
}
public void addChildren(Node node) {
this.getChildren().add(node);
}
}

View File

@ -0,0 +1,250 @@
package org.rococy.roomit.control.base;
import javafx.scene.Cursor;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import org.rococy.roomit.enums.RectangleCtrl;
/**
* 能改变尺寸的容器
*
* @author Rococy
* @date 2022/9/14
*/
public abstract class Resizer extends Pane {
/**
* 矩形原点
*/
private double originX, originY;
/**
* 记录调整完矩形后的大小
*/
private double width, height;
/**
* 当处于移动状态时记录的xy值
*/
private double moveX, moveY;
/**
* 矩形操作类型
*/
private RectangleCtrl rectangleCtrl;
/**
* 边框宽度
*/
private final double borderWidth = this.getBorderWidth();
/**
* @param originX 在整个屏幕内的初始x值
* @param originY 在整个屏幕内的初始y值
*/
public Resizer(double originX, double originY) {
// 记录原点
this.originX = originX;
this.originY = originY;
// 初始化调整大小事件
this.initEvents();
}
/**
* 获取边框宽度
*
* @return 边框宽度
*/
protected abstract double getBorderWidth();
/**
* 初始化矩形调整大小事件
*/
private void initEvents() {
this.bindMouseEnterEvent();
this.bindMousePressedEvent();
this.bindMouseMovedEvent();
this.bindMouseDraggedEvent();
this.bindMouseReleasedEvent();
this.bindKeyPressedEvent();
}
/**
* 鼠标进入
*/
private void bindMouseEnterEvent() {
}
/**
* 鼠标按下
*/
private void bindMousePressedEvent() {
this.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {
width = this.getWidth();
height = this.getHeight();
if (rectangleCtrl == RectangleCtrl.MOVE) {
moveX = e.getScreenX() - this.getLayoutX();
moveY = e.getScreenY() - this.getLayoutY();
}
});
}
/**
* 鼠标移动
*/
private void bindMouseMovedEvent() {
this.addEventHandler(MouseEvent.MOUSE_MOVED, e -> {
double width = this.getWidth(),
height = this.getHeight(),
x = e.getScreenX() - this.getLayoutX(),
y = e.getScreenY() - this.getLayoutY();
// topLeft
if (x <= borderWidth && y <= borderWidth) {
this.setCursor(Cursor.SE_RESIZE);
rectangleCtrl = RectangleCtrl.TOP_LEFT;
return;
}
// topMiddle
if (x <= width - borderWidth && y <= borderWidth) {
this.setCursor(Cursor.N_RESIZE);
rectangleCtrl = RectangleCtrl.TOP_MIDDLE;
return;
}
// topRight
if (x <= width && y <= borderWidth) {
this.setCursor(Cursor.NE_RESIZE);
rectangleCtrl = RectangleCtrl.TOP_RIGHT;
return;
}
// centerLeft
if (x <= borderWidth && y <= height - borderWidth) {
this.setCursor(Cursor.W_RESIZE);
rectangleCtrl = RectangleCtrl.CENTER_LEFT;
return;
}
// bottomLeft
if (x <= borderWidth && y <= height) {
this.setCursor(Cursor.NE_RESIZE);
rectangleCtrl = RectangleCtrl.BOTTOM_LEFT;
return;
}
// bottomMiddle
if (x <= width - borderWidth && y >= height - borderWidth) {
this.setCursor(Cursor.N_RESIZE);
rectangleCtrl = RectangleCtrl.BOTTOM_MIDDLE;
return;
}
// bottomRight
if (x <= width && y >= height - borderWidth) {
this.setCursor(Cursor.SE_RESIZE);
rectangleCtrl = RectangleCtrl.BOTTOM_RIGHT;
return;
}
// centerRight
if (x >= width - borderWidth && y <= height) {
this.setCursor(Cursor.W_RESIZE);
rectangleCtrl = RectangleCtrl.CENTER_RIGHT;
return;
}
// 移动指针
if (x >= borderWidth + 1 && x <= width - borderWidth && y >= borderWidth + 1 && y <= height - borderWidth) {
this.setCursor(Cursor.MOVE);
rectangleCtrl = RectangleCtrl.MOVE;
return;
}
this.setCursor(Cursor.DEFAULT);
});
}
/**
* 鼠标拖拽
*/
private void bindMouseDraggedEvent() {
this.addEventHandler(MouseEvent.MOUSE_DRAGGED, e -> {
// 防止快速操作
if (rectangleCtrl == null) {
return;
}
double screenX = e.getScreenX(),
screenY = e.getScreenY(),
x = screenX - originX,
y = screenY - originY;
switch (rectangleCtrl) {
case TOP_LEFT -> {
this.setLayoutX(screenX);
this.setLayoutY(screenY);
this.setPrefSize(width - x, height - y);
}
case TOP_MIDDLE -> {
this.setLayoutY(screenY);
this.setPrefHeight(height - y);
}
case TOP_RIGHT -> {
this.setLayoutY(screenY);
this.setPrefSize(x, height - y);
}
case CENTER_LEFT -> {
this.setLayoutX(screenX);
this.setPrefWidth(width - x);
}
case CENTER_RIGHT -> this.setPrefWidth(x);
case BOTTOM_LEFT -> {
this.setLayoutX(screenX);
this.setPrefSize(width - x, y);
}
case BOTTOM_MIDDLE -> this.setPrefHeight(y);
case BOTTOM_RIGHT -> {
this.setPrefSize(x, y);
}
case MOVE -> {
this.setLayoutX(screenX - moveX);
this.setLayoutY(screenY - moveY);
}
}
});
}
/**
* 鼠标抬起
*/
private void bindMouseReleasedEvent() {
this.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> {
this.originX = this.getLayoutX();
this.originY = this.getLayoutY();
});
}
/**
* 监听键盘按下
*/
private void bindKeyPressedEvent() {
}
}

View File

@ -3,19 +3,14 @@ package org.rococy.roomit.controller;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Border;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Shape;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import org.rococy.roomit.control.ResizableRectangle;
import org.rococy.roomit.control.Toast;
import org.rococy.roomit.domain.TransparentCanvas;
import org.rococy.roomit.control.*;
import org.rococy.roomit.symbol.BrushSymbol;
import org.rococy.roomit.symbol.CircleSymbol;
import org.rococy.roomit.symbol.RectangleSymbol;
@ -36,15 +31,10 @@ public class WindowController implements Initializable {
@FXML
private Pane pane;
private List<Node> paneChildren;
private final Group canvasGroup = new Group();
/**
* canvas配置
* 最外层pane的子元素集合
*/
private static final int TRANSPARENT_CANVAS_COUNT = 5;
private static boolean isTransparent = true;
private List<Node> paneChildren;
/**
* 画笔
@ -66,93 +56,151 @@ public class WindowController implements Initializable {
*/
private static Shape mouseLogo = BRUSH_SYMBOL;
/**
* 画笔
*/
private GraphicsContext gc;
/**
* 全局xy轴
* 画板xy轴
* 画矩形圆形的xy轴
*/
private double globalMouseX, globalMouseY, graphicStartX, graphicStartY, shapeStartX, shapeStartY;
private double globalMouseX, globalMouseY, shapeStartX, shapeStartY;
/**
* 状态1 画笔2 矩形3圆形
*/
private static int paintState = SymbolConsts.BRUSH;
/**
* 置顶置底
*/
private static final int TO_FRONT = 0;
private static final int TO_BACK = 100;
ResizableRectangle rectangle;
Pane rectanglePane;
ResizableEllipseWrapper resizableEllipseWrapper;
Ellipse ellipse;
private final CanvasGroup canvasGroup = new CanvasGroup();
private final ShapePane shapePane = new ShapePane();
@FXML
public void keyPressed(KeyEvent event) {
if (event.isControlDown()) {
switch (event.getCode()) {
case G -> paintState = SymbolConsts.BRUSH;
case G -> {
paintState = SymbolConsts.BRUSH;
switchPane();
}
case R -> {
paintState = SymbolConsts.RECTANGLE;
switchPane();
if (rectangle == null) {
rectanglePane = new Pane();
rectanglePane.setPrefSize(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight());
rectanglePane.setViewOrder(0);
shapePane.setOnMousePressed(e -> {
if (paintState == SymbolConsts.BRUSH || e.getTarget() != shapePane) {
return;
}
rectanglePane.setOnMousePressed(e -> {
if (paintState == SymbolConsts.BRUSH || e.getTarget() != rectanglePane) {
return;
}
shapeStartX = e.getX();
shapeStartY = e.getY();
rectangle = new ResizableRectangle(shapeStartX, shapeStartY);
shapePane.addChildren(rectangle);
shapeStartX = e.getX();
shapeStartY = e.getY();
rectangle.setLayoutX(shapeStartX);
rectangle.setLayoutY(shapeStartY);
});
rectangle = new ResizableRectangle(shapeStartX, shapeStartY);
rectanglePane.getChildren().add(rectangle);
shapePane.setOnMouseDragged(e -> {
if (paintState == SymbolConsts.BRUSH || e.getTarget() != shapePane) {
return;
}
double endX = e.getX();
double endY = e.getY();
// 向左上角方向拉伸检测
if (endX < shapeStartX && endY < shapeStartY) {
rectangle.setLayoutX(endX);
rectangle.setLayoutY(endY);
rectangle.setPrefSize(shapeStartX - endX, shapeStartY - endY);
} else if (endX < shapeStartX) {
// 直直往左拉伸检测
rectangle.setLayoutX(endX);
rectangle.setPrefSize(shapeStartX - endX, endY - shapeStartY);
} else if (endY < shapeStartY) {
// 直直往上拉伸检测
rectangle.setLayoutY(endY);
rectangle.setPrefSize(endX - shapeStartX, shapeStartY - endY);
} else {
// 正常拉伸矩形
rectangle.setPrefSize(endX - shapeStartX, endY - shapeStartY);
}
});
}
case Q -> {
paintState = SymbolConsts.CIRCLE;
switchPane();
shapePane.setOnMousePressed(e -> {
if (paintState == SymbolConsts.BRUSH || e.getTarget() != shapePane) {
return;
}
shapeStartX = e.getSceneX();
shapeStartY = e.getSceneY();
resizableEllipseWrapper = new ResizableEllipseWrapper(shapeStartX, shapeStartY);
ellipse = resizableEllipseWrapper.getInnerEllipse();
resizableEllipseWrapper.setLayoutX(shapeStartX);
resizableEllipseWrapper.setLayoutY(shapeStartY);
shapePane.addChildren(resizableEllipseWrapper);
});
shapePane.setOnMouseDragged(e -> {
if (paintState == SymbolConsts.BRUSH || e.getTarget() != shapePane) {
return;
}
double endX = e.getSceneX();
double endY = e.getSceneY();
// 向左上角方向拉伸检测
if (endX < shapeStartX && endY < shapeStartY) {
resizableEllipseWrapper.setLayoutX(endX);
resizableEllipseWrapper.setLayoutY(endY);
resizableEllipseWrapper.setPrefSize(shapeStartX - endX, shapeStartY - endY);
} else if (endX < shapeStartX) {
// 直直往左拉伸检测
resizableEllipseWrapper.setLayoutX(endX);
resizableEllipseWrapper.setPrefSize(shapeStartX - endX, endY - shapeStartY);
} else if (endY < shapeStartY) {
// 直直往上拉伸检测
resizableEllipseWrapper.setLayoutY(endY);
resizableEllipseWrapper.setPrefSize(endX - shapeStartX, shapeStartY - endY);
} else {
// 正常拉伸矩形
resizableEllipseWrapper.setPrefSize(endX - shapeStartX, endY - shapeStartY);
}
double centerX = resizableEllipseWrapper.getPrefWidth() / 2;
double centerY = resizableEllipseWrapper.getPrefHeight() / 2;
ellipse.setCenterX(centerX);
ellipse.setCenterY(centerY);
ellipse.setRadiusX(centerX - resizableEllipseWrapper.getEllipseIntervals());
ellipse.setRadiusY(centerY - resizableEllipseWrapper.getEllipseIntervals());
});
shapePane.setOnMouseReleased(e -> {
if (e.getScreenX() >= resizableEllipseWrapper.getLayoutX() + resizableEllipseWrapper.getPrefWidth()
&& e.getScreenY() >= resizableEllipseWrapper.getLayoutY() + resizableEllipseWrapper.getPrefHeight()) {
resizableEllipseWrapper.setBorder(Border.EMPTY);
}
});
rectangle.setLayoutX(shapeStartX);
rectangle.setLayoutY(shapeStartY);
});
rectanglePane.setOnMouseDragged(e -> {
if (paintState == SymbolConsts.BRUSH || e.getTarget() != rectanglePane) {
return;
}
double endX = e.getX();
double endY = e.getY();
// 向左上角方向拉伸检测
if (endX < shapeStartX && endY < shapeStartY) {
rectangle.setLayoutX(endX);
rectangle.setLayoutY(endY);
rectangle.setWidth(shapeStartX - endX);
rectangle.setHeight(shapeStartY - endY);
} else if (endX < shapeStartX) {
// 直直往左拉伸检测
rectangle.setLayoutX(endX);
rectangle.setWidth(shapeStartX - endX);
rectangle.setHeight(endY - shapeStartY);
} else if (endY < shapeStartY) {
// 直直往上拉伸检测
rectangle.setLayoutY(endY);
rectangle.setWidth(endX - shapeStartX);
rectangle.setHeight(shapeStartY - endY);
} else {
// 正常拉伸矩形
rectangle.setWidth(endX - shapeStartX);
rectangle.setHeight(endY - shapeStartY);
}
});
paneChildren.add(rectanglePane);
}
}
case Q -> paintState = SymbolConsts.CIRCLE;
}
changeMouseLogo();
@ -172,7 +220,6 @@ public class WindowController implements Initializable {
changeMouseLogo();
}
/**
* 初始化控件
*
@ -181,11 +228,11 @@ public class WindowController implements Initializable {
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
initPane();
initCanvasSheet();
initPaneWrapper();
initDrawingBoard();
}
private void initPane() {
private void initPaneWrapper() {
pane.setPrefSize(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenWidth());
paneChildren = pane.getChildren();
paneChildren.add(new Toast("屏幕工具已开启", 500));
@ -206,85 +253,25 @@ public class WindowController implements Initializable {
pane.setOnMouseMoved(paneMouseEventHandler);
}
private void initCanvasSheet() {
if (isTransparent) {
// 初始化层叠Canvas
for (int i = 0; i < TRANSPARENT_CANVAS_COUNT; i++) {
TransparentCanvas canvas = new TransparentCanvas();
canvas.setViewOrder(i);
bindCanvasEvents(canvas);
bindCanvasTransparentEvents(canvas);
canvasGroup.getChildren().add(canvas);
}
} else {
// 直接给一个Canvas即可
TransparentCanvas canvas = new TransparentCanvas();
bindCanvasEvents(canvas);
canvasGroup.getChildren().add(canvas);
}
canvasGroup.setViewOrder(100);
private void initDrawingBoard() {
paneChildren.add(canvasGroup);
paneChildren.add(shapePane);
canvasGroup.setViewOrder(TO_FRONT);
shapePane.setViewOrder(TO_BACK);
}
private void bindCanvasTransparentEvents(TransparentCanvas canvas) {
// 鼠标抬起开始透明操作
canvas.setOnMouseReleased(e -> {
// 当前画完的Canvas开始变透明
canvas.startTransparency();
List<Node> nodeList = canvasGroup.getChildren();
for (Node node : nodeList) {
if (!(node instanceof TransparentCanvas)) {
continue;
}
// 即将置于顶层的canvas
TransparentCanvas topCanvas = (TransparentCanvas) node;
// 正在隐藏当中
if (!topCanvas.isUsed() || topCanvas == canvas) {
continue;
}
// 交换图形层叠位置
canvas.setViewOrder(topCanvas.getViewOrder());
topCanvas.setViewOrder(0);
break;
private void switchPane() {
switch (paintState) {
case SymbolConsts.RECTANGLE, SymbolConsts.CIRCLE -> {
shapePane.setViewOrder(TO_FRONT);
canvasGroup.setViewOrder(TO_BACK);
}
});
}
public void bindCanvasEvents(TransparentCanvas canvas) {
// 鼠标按下
canvas.setOnMousePressed(e -> {
graphicStartX = e.getX();
graphicStartY = e.getY();
gc = canvas.getGraphicsContext2D();
});
// 鼠标按下并拖动
canvas.setOnMouseDragged(e -> {
double endX = e.getX();
double endY = e.getY();
gc.setStroke(Paint.valueOf("#f00"));
// 让线变得圆滑
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
// 线条宽度
gc.setLineWidth(5);
// 画线
gc.strokeLine(graphicStartX, graphicStartY, endX, endY);
// 连续画线
graphicStartX = endX;
graphicStartY = endY;
});
case SymbolConsts.BRUSH -> {
shapePane.setViewOrder(TO_BACK);
canvasGroup.setViewOrder(TO_FRONT);
}
}
}
private void changeMouseLogo() {

View File

@ -1,52 +0,0 @@
package org.rococy.roomit.domain;
import javafx.scene.canvas.Canvas;
import org.rococy.roomit.util.ScreenUtils;
import java.util.Timer;
import java.util.TimerTask;
/**
* 自动透明的Canvas
*
* @author Rococy
* @date 2022/9/13
*/
public class TransparentCanvas extends Canvas {
private double opacity = 1;
private boolean used = true;
public TransparentCanvas() {
super(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight());
}
public void startTransparency() {
used = false;
new Timer().schedule(new OpacityTask(), 0, 100);
}
public boolean isUsed() {
return used;
}
private class OpacityTask extends TimerTask {
private final TransparentCanvas canvas = TransparentCanvas.this;
@Override
public void run() {
opacity *= 0.9;
canvas.setOpacity(opacity);
if (opacity < 0.1) {
used = true;
opacity = 1;
canvas.getGraphicsContext2D().clearRect(0,0, canvas.getWidth(), canvas.getHeight());
canvas.setOpacity(opacity);
this.cancel();
}
}
}
}

View File

@ -8,6 +8,10 @@ import javafx.scene.shape.Circle;
*/
public abstract class BaseCircleSymbol extends Circle implements MouseSymbol {
public BaseCircleSymbol() {
this.setViewOrder(1000);
}
@Override
public BaseCircleSymbol position(double x, double y) {
this.setCenterX(x);

View File

@ -8,6 +8,10 @@ import javafx.scene.shape.Rectangle;
*/
public abstract class BaseRectangleSymbol extends Rectangle implements MouseSymbol {
public BaseRectangleSymbol() {
this.setViewOrder(1000);
}
@Override
public BaseRectangleSymbol position(double x, double y) {
this.setX(x);