From Spiral01 To Spiral02

The code for the Model Concepts project is at model_concepts on GitHub.

The code for the Magic Boxes project is at mb_spirals on GitHub. For teaching or learning purposes, each spiral in Magic Boxes is decomposed into several sub-spirals.

In Spiral 01, two static boxes were displayed on the board. In Spiral 02, three boxes may be moved within the board.

The Timer.periodic constructor of the Timer class from the dart:async library is used to define a refresh of displayed objects. The redraw method of the Board class will be called every INTERVAL milliseconds (ms) to display rectangles in new positions.

Code 01-02.01: Interval.

Board(CanvasElement canvas) {
    context = canvas.getContext("2d");
    width = canvas.width;
    height = canvas.height;
    border();
    init();
    // Redraw every 10 ms.
    new Timer.periodic(const Duration(milliseconds: 10), (t) => redraw());
  }

In the Box class, a reaction to a mouse move event is defined. The event reaction is detailed in the onMouseMove method.

Code 01-02.02: Destination of the mouse move event.

// Change a position of the box with mouse mouvements.
  void onMouseMove(MouseEvent event) {
    x = event.offset.x;
    y = event.offset.y;
  }

The method is used in the constructor of the Box class. The mouse move event will be listened by the document and the onMouseMove method will be executed with each move of the mouse.

Code 01-02.03: Source of the mouse move event.

Box(this.board, this.x, this.y, this.width, this.height) {
    draw();
    document.onMouseMove.listen(onMouseMove);
  }

The onMouseMove method has the event parameter, of the MountEvent class, which is supplied by Dart. The offset.x attribute of the event object gives an x coordinate of the mouse. The offset is calculated relative to the x = 0 coordinate of the board (canvas) object. The offset attribute of the event object gives a y coordinate of the mouse. The offset is calculated relative to the y = 0 coordinate of the board (canvas) object. With each mouse move, one of boxes will change its position with the update of its x and y coordinates (the attributes of the Box class). Since the board will be redrawn very quickly, each new position of the same box will appear as a new box (Figure 01-02.01).

Alt Figure 01-02.01: “Many” boxes

Figure 01-02.01: “Many” boxes.

In order to show only new positions of boxes, the board must be cleared from the previous positions. Note that there are now three boxes (two horizontal boxes and one vertical box) on the board.

Code 01-02.04: Clearing the board.

class Board {

  static const int INTERVAL = 10; // ms

  int width;
  int height;

  CanvasRenderingContext2D context;

  Box box1;
  Box box2;
  Box box3;

  Board(CanvasElement canvas) {
    context = canvas.getContext("2d");
    width = canvas.width;
    height = canvas.height;
    border();
    init();
    // Redraw every INTERVAL ms.
    new Timer.periodic(const Duration(milliseconds: INTERVAL), (t) => redraw());
  }

  void init() {
    box1 = new Box(this, 20, 20, 80, 40);
    box2 = new Box(this, 120, 220, 40, 80);
    box3 = new Box(this, 60, 80, 80, 40);
  }

  void border() {
    context.beginPath();
    context.rect(0, 0, width, height);
    context.closePath();
    context.stroke();
  }

  void clear() {
    context.clearRect(0, 0, width, height);
    border();
  }

  void redraw() {
    clear();
    box1.draw();
    box2.draw();
    box3.draw();
  }

}

The clear method clears the rectangle that is occupied by the context of the canvas, and the border is drawn again. The clear method is called in the redraw method before the three boxes are drawn. The interval of redrawing the board is still 10 ms, but it is defined at the beginning of the Board class as the const integer. The initial value cannot be changed, representing a constant. A name of a constant is expressed in capital letters. The INTERVAL constant, with the value of 10, is used in the Duration constructor.

When the code is run in the Chrome browser, one of horizontal boxes is “eaten” by the other horizontal box, and the vertical box is captured as a prisoner of the two horizontal boxes.

The isPointInside method returns true, if the (x, y) point is inside the current box.

Code 01-02.05: Is point inside of the current box?

bool isPointInside(int px, int py) {
    if ((px > x && px < x + width) && (py > y && py < y + height)) {
      return true;
    }
    return false;
  }

The Boolean method checks if a position of the mouse is inside the box of interest, and only then the box will “move”. However, the box may be moved only down and right.

Code 01-02.06: Is mouse inside of the current box?

// Change a position of the box with mouse mouvements.
  void onMouseMove(MouseEvent event) {
    if (isPointInside(event.offset.x, event.offset.y)) {
      x = event.offset.x;
      y = event.offset.y;
    }
  }

The new position of the current box will be now in the middle of the box. This allows a user to move the box also up and left. However, the box may be moved outside the board.

Code 01-02.07: Box center.

// Change a position of the box with mouse mouvements.
  void onMouseMove(MouseEvent event) {
    if (isPointInside(event.offset.x, event.offset.y)) {
      x = event.offset.x - width / 2;
      y = event.offset.y - height / 2;
    }
  }

The upper left corner of a box (x, y) is displayed close to that corner, so that you can check the exact positions of boxes (Figure 01-02.02) .

Alt Figure 01-02.02: (x, y) displayed.

Figure 01-02.02: (x, y) displayed.

Code 01-02.08: x, y displayed in the upper left corner.

void draw() {
    board.context.beginPath();
    board.context.rect(x, y, width, height);
    board.context.fillText('${x.toString()},${y.toString()}', x + 1, y + 10);
    board.context.closePath();
    board.context.stroke();
  }

In order to keep boxes inside the board, a new position of the current box is brought back to the board, if it was found outside the board.

Code 01-02.09: Back to board.

// Change a position of the box with mouse mouvements.
  void onMouseMove(MouseEvent event) {
    if (isPointInside(event.offset.x, event.offset.y)) {
      x = event.offset.x - width / 2;
      if (x < 0) x = 1;
      if (x > board.width - width) x = board.width - width - 1;
      y = event.offset.y - height / 2;
      if (y < 0) y = 1;
      if (y > board.height - height) y = board.height - height - 1;
    }
  }

Since there is only one statement in the body of ifs inside the outer if of the onMouseMove method, there is no need to use {}.

Explain what happens if the following code is used instead.

Code 01-02.10: Different coordinates.

if (x < 0) x = 0;
      if (x > board.width - width) x = board.width - width;
      
      if (y < 0) y = 0;
      if (y > board.height - height) y = board.height - height;
blog comments powered by Disqus