目录

Part1拓展

拓展需求:

需求分步:

具体实现

1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展

2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标

3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配


 此部分是基于对第一部分功能的拓展!

Part1拓展

拓展需求:

1.1 用户可以添加/删除单个目标/从选择通过控制点击

1.2 用户可以使用讲座中讨论的“橡皮筋-套索”组合方法选择多个目标

1.2.1 简化使用橡皮筋或套索的决定,以选择更多的项目

1.3 用户可以通过按下控制键和使用橡皮带套索控制来添加/删除多个目标

1.4 当多个目标被选中时,操作会影响所有选中的项目(移动,调整大小,删除)

需求分步:

1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展

2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标

3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配

具体实现

1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展

思路:实现多选功能就肯定需要一个列表对选中的目标进行存储,但之前的单选也应该保留,所以我就想到普通单选和多选可以分开存储,那么就必须保证单选和多选不会起冲突(不可以同时有单选和多选两种情况)

实现方法:

1.在iModel类中添加多选列表,并添加相关方法,包括增删改查以及Blob是否在列表中等方法。

public class InteractionModel {
  
	  Blob selected;
  	// 多选列表
    List<Blob> selectedBlobs = new ArrayList<>();
   
    public void setSelected(Blob b) {
        clearSelectedBlobs();
        selected = b;
        notifySubscribers();
    }

    public void unselect() {
        selected = null;
        notifySubscribers();
    }

    public Blob getSelected() {
        return selected;
    }

    // 多选列表操作的相关方法
    public void setSelectedBlobs(List<Blob> blobs){
        selectedBlobs = blobs;
        notifySubscribers();
    }

    public void addSelectedBlobs(Blob blob){
        selectedBlobs.add(blob);
        notifySubscribers();
    }

    public void deleteSelectedBlob(Blob blob){
        Iterator<Blob> it = selectedBlobs.iterator();
        while (it.hasNext()) {
            Blob s = it.next();
            if (blob.equals(s)) {
                it.remove();
            }
        }
        notifySubscribers();
    }

    public void clearSelectedBlobs(){
        selectedBlobs.clear();
        notifySubscribers();
    }

    public List<Blob> getSelectedBlobs(){
        return selectedBlobs;
    }

    public boolean containedInSelected(Blob blob){
        Iterator<Blob> it = selectedBlobs.iterator();
        while (it.hasNext()) {
            Blob s = it.next();
            if (blob.equals(s)) {
                return true;
            }
        }
        return false;
    }
    
   
}

2.在View类中的画操作中添加对多选目标的显示

仅仅只需要对imodel的多选列表中的对象显示即可

public class BlobView extends StackPane implements BlobModelListener, IModelListener {
	private void draw() {
        gc.clearRect(0,0,myCanvas.getWidth(),myCanvas.getHeight());
        model.getBlobs().forEach(b -> {
          	// 仅仅只需要对imodel的多选列表中的对象显示即可
            if (b == iModel.getSelected() || iModel.containedInSelected(b)) {
                gc.setFill(Color.TOMATO);
            } else {
                gc.setFill(Color.BEIGE);
            }
            gc.fillOval(b.x-b.r,b.y-b.r,b.r*2,b.r*2);
            gc.setFill(Color.BLACK);
            gc.fillText(String.valueOf(b.getCount()), b.x-3,b.y+3);
        });
    }
}

3.修改controller类中的多选操作以及对单选与多选逻辑上修改即可

鼠标按压时,判断按压背景还是某个圆,是某个圆,则就是选择操作

选择 从是否按压Ctrl上可以区分为多选还是单选。

若是多选 则通过判断该圆是否被选中后 对圆进行选中/取消(为了分开单选和多选,在多选时就要将单选取消)

若是单选 若此时多选不为空(即正在进行多选操作,就单选不成功)

为空 就可以正常使用

之后的每一个增加,删除,修改操作都是对两种情况进行判断

若单选 正常操作即可

多选 对多个目标边里进行操作即可

public class BlobController {
    BlobModel model;
    InteractionModel iModel;
    double prevX,prevY;
    double dX,dY;

    enum State {READY,PREPARE_CREATE, DRAGGING, DRAGGING_TOOL}
    State currentState = State.READY;

    public void handlePressed(MouseEvent event) {
        switch (currentState) {
            case READY -> {
                if (model.hitBlob(event.getX(),event.getY())) {        // 选中⚪
                    Blob b = model.whichHit(event.getX(),event.getY());
                    // 对多选和单选就行区分
                    if (event.isControlDown()){
                        if (iModel.containedInSelected(b)){
                            iModel.deleteSelectedBlob(b);
                        }else {
                            iModel.addSelectedBlobs(b);
                            iModel.unselect();
                        }
                    }else if (iModel.getSelectedBlobs().isEmpty()){
                        iModel.setSelected(b);
                    }
                    prevX = event.getX();
                    prevY = event.getY();
                    currentState = State.DRAGGING;                    // 选中之后就改变状态为dragging
                } else {
                    if (event.isControlDown()){
                        iModel.clearPoints();
                        iModel.setPathComplete(false);
                        System.out.println("设置起点");
                        iModel.setInitialPointX(event.getX());
                        iModel.setInitialPointY(event.getY());
                        iModel.addPoint(new Point2D(event.getX(), event.getY()));

                        System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY());
                        currentState = State.DRAGGING_TOOL;
                    } else {
                        currentState = State.PREPARE_CREATE;               // 未选中 就意味着在释放时要创建新的⚪
                    }


                }
            }
        }
    }

    public void handleDragged(MouseEvent event) {      // 鼠标拖动
        switch (currentState){
            case PREPARE_CREATE -> {
                currentState = State.READY;
            }
            case DRAGGING -> {

                if (!event.isShiftDown()){
                    // 拖动被选中的⚪
                    dX = event.getX() - prevX;
                    dY = event.getY() - prevY;
                    prevX = event.getX();
                    prevY = event.getY();

                    if (iModel.getSelected() != null){
                        System.out.println("移动之前"+iModel.getSelected());
                        BlobAction blobAction = BlobAction.DraggedBlobRecord(iModel.getSelected());
                        model.moveBlob(iModel.getSelected(), dX,dY);
                        System.out.println("移动之后"+iModel.getSelected());
                        // 创建一个移动操作
                        List<BlobAction> blobActions = iModel.addBlobAction(iModel.createBlobActions(), blobAction);
                        iModel.registerUndoStack(blobActions);
                        iModel.clearRedoStack();

                    }else {

                      	// 同时移动多个选中目标
                        List<BlobAction> blobActions = iModel.createBlobActions();

                        List<Blob> selectedBlobs = iModel.getSelectedBlobs();
                        selectedBlobs.forEach(b -> {
                            BlobAction blobAction = BlobAction.DraggedBlobRecord(b);
                            blobActions.add(blobAction);
                        });
                        model.moveBlobs(selectedBlobs, dX, dY);

                        // 创建多个移动操作
                        iModel.registerUndoStack(blobActions);
                        iModel.clearRedoStack();
                        System.out.println("多个移动栈"+blobActions);
                    }

                }else{
                    // 更改⚪的大小
                    dX = event.getX() - prevX;

                    if (iModel.getSelected() != null){
                        Blob blob = iModel.getSelected();
                        BlobAction blobAction = BlobAction.MotifiedBlobRecord(blob);
                        model.motifyR(blob,dX);
                        // 创建更改大小操作
                        List<BlobAction> blobActions = iModel.addBlobAction(iModel.createBlobActions(), blobAction);
                        iModel.registerUndoStack(blobActions);
                        iModel.clearRedoStack();
                    }else {
                        List<BlobAction> blobActions = iModel.createBlobActions();
                      	// 修改多选的多个圆大小
                        List<Blob> selectedBlobs = iModel.getSelectedBlobs();
                        selectedBlobs.forEach(b -> {
                            BlobAction blobAction = BlobAction.MotifiedBlobRecord(b);
                            blobActions.add(blobAction);
                        });

                        model.motifyRs(selectedBlobs, dX);
                        iModel.registerUndoStack(blobActions);
                        iModel.clearRedoStack();
                        System.out.println("多个更改大小栈"+blobActions);
                    }


                }

            }
        }
    }

    public void handleReleased(MouseEvent event) {       // 鼠标释放
        switch (currentState) {
            case PREPARE_CREATE -> {                     // 创建一个⚪
                if (event.isShiftDown()){
                    Blob blob = model.addBlob(event.getX(), event.getY());
                    currentState = State.READY;
                    // 注册一个创建操作
                    List<BlobAction> blobActions = iModel.addBlobAction(iModel.createBlobActions(), new BlobAction(blob,"CREATE"));
                    iModel.registerUndoStack(blobActions);
                    iModel.clearRedoStack();
                }else if (event.isControlDown()){
                    // 删除所有⚪
                    model.clear();
                    iModel.clearUndoStack();
                    iModel.clearRedoStack();
                }else {
                    iModel.unselect();
                    iModel.clearSelectedBlobs();
                }

            }
            case DRAGGING -> {
//                iModel.unselect();
                currentState = State.READY;
            }
            case  DRAGGING_TOOL -> {
                currentState = State.READY;
                iModel.setPathComplete(true);
            }
        }
    }

    public void keyHandlePressed(KeyEvent event) {       // 键盘输入
        System.out.println(event.getCode());
        List<BlobAction> blobActions;
        if (event.getCode() == KeyCode.DELETE){
            System.out.println("正在执行删除操作");
            System.out.println(iModel.getSelected());
            System.out.println(currentState);
            if (iModel.getSelected() != null){
                System.out.println("删除单个选中目标");
                model.deleteBlob(iModel.getSelected());
                // 注册删除操作   blob   type
                blobActions = iModel.addBlobAction(iModel.createBlobActions(), new BlobAction(iModel.getSelected(), "DELETE"));
                iModel.registerUndoStack(blobActions);
                iModel.clearRedoStack();
            }else {
                System.out.println("删除多个选中目标");
                blobActions = iModel.createBlobActions();

                List<Blob> selectedBlobs = iModel.getSelectedBlobs();
                selectedBlobs.forEach(b -> {
                    BlobAction blobAction = new BlobAction(b,"DELETE");
                    blobActions.add(blobAction);
                });
                model.deleteBlobs(selectedBlobs);
                iModel.registerUndoStack(blobActions);
                iModel.clearRedoStack();

                System.out.println("多个删除栈"+blobActions);
            }

        }
    }

    
}
2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标

1.实现Ctrl + 单击 (上面其实已经实现了)

2.“橡皮筋-套索”组合方法选择多个目标

1.矩形

思路:在鼠标按压时,设置起始位置点,在拖动时,鼠标的位置就是终止点。这两个点就是矩形的对角点。然后通过这两个点在视图层中画出矩形

实现步骤:

1.在iModel类中添加起始点和游标位置的存储

设置游标点时,view层就要刷新,所以要添加检测方法dragNotifySubscribers();

public class InteractionModel {
  	// 起始位置
		double initialPointX,initialPointY;
    // 游标位置
    double cursorX,cursorY;

    public void setCursorXY(double cursorX,double cursorY) {
        this.cursorX = cursorX;
        this.cursorY = cursorY;
        dragNotifySubscribers();
    }

    get/set方法。。。。
}

2.在View类中画出矩形,并设置动态检测方法

(1).在检测接口类添加检测方法

public interface IModelListener {
    void iModelChanged();

    void iModelDragChanged();
}

(2).在视图类中实现该方法并画出矩形

public class BlobView extends StackPane implements BlobModelListener, IModelListener {
		private void drawDrag() {
        // 矩形
        if (!iModel.isPathComplete()){
            gc.setStroke(Color.GREEN);
            gc.strokeRect(Math.min(iModel.getInitialPointX(),iModel.getCursorX()),
                    Math.min(iModel.getInitialPointY(),iModel.getCursorY()),
                    Math.abs(iModel.getCursorX() - iModel.getInitialPointX()),
                    Math.abs(iModel.getCursorY() - iModel.getInitialPointY()));
        }
    }

		@Override
    public void iModelChanged() {
        draw();
        drawDrag();
    }

    @Override
    public void iModelDragChanged() {
        draw();
        drawDrag();
    }

}

(3)iModel类中设置检测方法(也就是第一步中用于监测的方法)

public class InteractionModel {
  	// 对矩形之外的图形进行监测
    private void notifySubscribers() {
        subscribers.forEach(s -> s.iModelChanged());
    }
  	// 对矩形进行监测
    private void dragNotifySubscribers() {
        subscribers.forEach(s -> s.iModelDragChanged());
    }
}

3.控制层中实现矩形多选圆的算法

该算法很简单就是判断圆心是否在矩形中,并且圆心到四条边的距离是否大于圆的半径即可判断

public boolean isBlobInStrokeRect(Blob blob){

        if (!(blob.getX() > iModel.getInitialPointX() == blob.getX() < iModel.getCursorX())){
            return false;
        }
        if (!(blob.getY() > iModel.getInitialPointY() == blob.getY() < iModel.getCursorY())){
            return false ;
        }
        if (Math.abs(blob.getX() - iModel.getInitialPointX()) > blob.getR() && Math.abs(blob.getX() - iModel.getCursorX()) > blob.getR()
        && Math.abs(blob.getY() - iModel.getInitialPointY()) > blob.getR() && Math.abs(blob.getY() - iModel.getCursorY()) > blob.getR()){
            return true;
        }
        return false;

    }

4.就是在controller增重设置起始点与游标点即可

拖动时设置新的状态机 DRAGGING_TOOL 用于画橡皮筋和矩形时使用(和更改大小,拖动等操作区分开)

public class BlobController {
		public void handlePressed(MouseEvent event) {
        switch (currentState) {
            case READY -> {
                if (model.hitBlob(event.getX(),event.getY())) {        // 选中⚪
                    ..........                   // 选中之后就改变状态为dragging
                } else {
                    if (event.isControlDown()){
                        iModel.clearPoints();
                        iModel.setPathComplete(false);
                        System.out.println("设置起点");

												//  按压时添加起始点
                        iModel.setInitialPointX(event.getX());
                        iModel.setInitialPointY(event.getY());

                        iModel.addPoint(new Point2D(event.getX(), event.getY()));

                        System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY());
                        currentState = State.DRAGGING_TOOL;
                    } else {
                        currentState = State.PREPARE_CREATE;               // 未选中 就意味着在释放时要创建新的⚪
                    }
                }
            }
        }
    }
		public void handleDragged(MouseEvent event) {      // 鼠标拖动
        switch (currentState) {

            case DRAGGING_TOOL -> {
                if (event.isControlDown()){
                    iModel.clearSelectedBlobs();
                    System.out.println("设置游标点");

                    iModel.setCursorXY(event.getX(), event.getY());
                    iModel.addPoint(new Point2D(event.getX(), event.getY()));
                    // 设置之后对所有圆遍历判断 是否在矩形中
										model.getBlobs().forEach(b -> {
                        System.out.println("开始运行。。。");
                        if (isBlobInStrokeRect(b)){
                            // 是 则被选中
                            iModel.addSelectedBlobs(b);
                        }
                        if (isBlobInPoints(b)){
                            iModel.addSelectedBlobs(b);
                        }
                    });
                }
            }
        }
    }
}

2.橡皮筋-套索”

思路:就是将鼠标移动时的点全纪录下来,通过这些点判断圆是否被圈中

实现步骤:

1.将鼠标移动时的点记录在iModel类中,同时使用pathComplete来判断在鼠标移动过程以及鼠标按压/释放状态

刚按压时 pathComplete -> false

过程中 false

释放时 true

public class InteractionModel {
		//  记录鼠标移动时的点 
  	List<Point2D> points = new ArrayList<>();

    boolean pathComplete;

    public boolean isPathComplete() {
        return pathComplete;
    }

    public void setPathComplete(boolean pathComplete) {
        this.pathComplete = pathComplete;
        dragNotifySubscribers();
        if (pathComplete == true){
            notifySubscribers();
        }
    }

    public List<Point2D> getPoints() {
        return points;
    }

    public void addPoint(Point2D point){
        points.add(point);
        dragNotifySubscribers();
    }

    public void clearPoints(){
        points.clear();
        dragNotifySubscribers();
    }
}

2.通过算法判断圆是否被圈中

JavaFX获取resouce javafx获取鼠标点击位置_JavaFX获取resouce

算法思路:

首先排除经过的点有经过圆的。

然后将圆心放在坐标轴中心,四个象限必须全部有经过的点才算作被包裹。(可以使用的大多数情况)

JavaFX获取resouce javafx获取鼠标点击位置_List_02

但是也有例外。就是经过圆四周但是不包裹圆的情况,这个属于算法上的缺陷

JavaFX获取resouce javafx获取鼠标点击位置_java_03

如上图,就无法判定圆不在索套中

public boolean isBlobInPoints(Blob blob){
        List<Integer> list = new ArrayList<>();
        List<Point2D> points = iModel.getPoints();
        for (Point2D point : points) {
            if (getLength(blob,point) < blob.getR()) {
                return false;
            }
            if (point.getX() < blob.getX() && point.getY() < blob.getY()){
                if (list.size() == 0){
                    list.add(1);
                }
                if (list.size() != 0 && list.get(list.size() - 1) != 1){
                    list.add(1);
                }
            }
            if (point.getX() > blob.getX() && point.getY() < blob.getY()){
                if (list.size() == 0){
                    list.add(2);
                }
                if (list.size() != 0 && list.get(list.size() - 1) != 2){
                    list.add(2);
                }
            }
            if (point.getX() > blob.getX() && point.getY() > blob.getY()){
                if (list.size() == 0){
                    list.add(3);
                }
                if (list.size() != 0 && list.get(list.size() - 1) != 3){
                    list.add(3);
                }
            }
            if (point.getX() < blob.getX() && point.getY() > blob.getY()){
                if (list.size() == 0){
                    list.add(4);
                }
                if (list.size() != 0 && list.get(list.size() - 1) != 4){
                    list.add(4);
                }
            }
        }
        if (list.size() >= 4 && list.contains(1) && list.contains(2) && list.contains(3) && list.contains(4)){
            return true;
        }
        return false;
    }

3.在controller类中拖动时将点记录在iModel中

public class BlobController {
		public void handlePressed(MouseEvent event) {
        switch (currentState) {
            case READY -> {
                if (model.hitBlob(event.getX(),event.getY())) {        // 选中⚪
                    Blob b = model.whichHit(event.getX(),event.getY());
                    if (event.isControlDown()){
                        if (iModel.containedInSelected(b)){
                            iModel.deleteSelectedBlob(b);
                        }else {
                            iModel.addSelectedBlobs(b);
                            iModel.unselect();
                        }
                    }else if (iModel.getSelectedBlobs().isEmpty()){
                        iModel.setSelected(b);
                    }
                    prevX = event.getX();
                    prevY = event.getY();
                    currentState = State.DRAGGING;                    // 选中之后就改变状态为dragging
                } else {
                    if (event.isControlDown()){

                        iModel.clearPoints();
                        // 按压时将pathComplete ->  false
                        iModel.setPathComplete(false);
                        System.out.println("设置起点");
                        iModel.setInitialPointX(event.getX());
                        iModel.setInitialPointY(event.getY());
                      	// 记录起始点
                        iModel.addPoint(new Point2D(event.getX(), event.getY()));

                        currentState = State.DRAGGING_TOOL;
                    } else {
                        currentState = State.PREPARE_CREATE;               // 未选中 就意味着在释放时要创建新的⚪
                    }
                }
            }
        }
    }
		public void handleDragged(MouseEvent event) {      // 鼠标拖动
        switch (currentState) {

            case DRAGGING_TOOL -> {
                if (event.isControlDown()){
                    iModel.clearSelectedBlobs();
                    System.out.println("设置游标点");
                    iModel.setCursorXY(event.getX(), event.getY());
                    // 添加拖动时的点
                    iModel.addPoint(new Point2D(event.getX(), event.getY()));
                    System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY());
                    System.out.println("游标点:"+iModel.getCursorX()+","+iModel.getCursorY());
                    model.getBlobs().forEach(b -> {
                        if (isBlobInStrokeRect(b)){
                            iModel.addSelectedBlobs(b);
                        }
                      	// 判断是否被套中
                        if (isBlobInPoints(b)){
                            iModel.addSelectedBlobs(b);
                        }
                    });
                }
            } 
        }
    }
}

4.将imodel记录的点在view层显示出来

public class BlobView extends StackPane implements BlobModelListener, IModelListener {
	private void drawDrag() {
        // 矩形
        if (!iModel.isPathComplete()){
            gc.setStroke(Color.GREEN);
            gc.strokeRect(Math.min(iModel.getInitialPointX(),iModel.getCursorX()),
                    Math.min(iModel.getInitialPointY(),iModel.getCursorY()),
                    Math.abs(iModel.getCursorX() - iModel.getInitialPointX()),
                    Math.abs(iModel.getCursorY() - iModel.getInitialPointY()));
        }

        // 橡皮筋
        if (!iModel.isPathComplete()) {
            gc.setFill(Color.DARKGRAY);
            iModel.getPoints().forEach(p -> gc.fillOval(p.getX()-3,p.getY()-3,6,6));
        }
    }
}
3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配

思路:将撤销重做栈 和 剪切板 修改为列表数据类型即可,之前的所有操作都修改为基于列表的操作即可。就是个体力活,不多介绍啦。