DekGenius.com
[ Team LiB ] Previous Section Next Section

22.3 Assembling the Flash Paint Application

You have now created all the necessary components for the Flash Paint application. The only remaining step is to create the main routine that puts them all together. To accomplish this, add the following code to the first frame of the main timeline of the flashPaint.fla document:

// Include MovieClip.as from Chapter 7, DrawingMethods.as from Chapter 4, and
// Table.as from Chapter 11 for their custom methods.
#include "MovieClip.as"
#include "DrawingMethods.as"
#include "Table.as"

// Initialize the movie.
function init(  ) {
  selectedShape = null;
  selectedTool = null;
  shapes = new Array(  );
  makeTools(  );
}

// The makeTools(  ) method creates the toolbar buttons.
function makeTools (  ) {

  // Define an array of the first six tool names. These names match the accepted
  // values within the toolbar button component.
  toolbarBtnNames = ["select", "line", "rectangle", "ellipse", "text", "fill"];

  // Create an array to hold references to the "stick" toolbar button instances.
  toolbarBtns = new Array(  );

  // Create a toolbar movie clip to contain all the tools (including 
  // the color selector) and a nested btns movie clip to specifically 
  // contain the toolbar buttons.
  _root.createEmptyMovieClip("toolbar", _root.getNewDepth(  ));
  toolbar.createEmptyMovieClip("btns", toolbar.getNewDepth(  ));
  var name, btn;

  // Create a table for holding the buttons. Set the row spacing to three pixels.
  t = new Table(3);

  // Loop through all the names of the buttons.
  for (var i = 0; i < toolbarBtnNames.length; i++) {
    name = toolbarBtnNames[i];

    // Create the toolbar button instance.
    btn = toolbar.btns.attachMovie("ToolbarButtonSymbol", 
                                    name + "Btn", 
                                    toolbar.getNewDepth(  ), 
                                    {val: name});
    // Call the create(  ) method for each of the "stick" buttons. Set the onSelect(  ) 
    // callback function for each.
    btn.create(name, "stick");
    btn.setOnSelect("onSelectTool", _root);

    // Add the button to the array and to a table column and row.
    toolbarBtns.push(btn);
    t.addRow(new TableRow(0, new TableColumn(0, btn)));
  }

  // Create the back and forward buttons, which are "spring" buttons.
  toolbar.btns.attachMovie("ToolbarButtonSymbol", "backBtn", 
                            toolbar.getNewDepth(  ), {val: name});
  toolbar.btns.attachMovie("ToolbarButtonSymbol", "forwardBtn", 
                            toolbar.getNewDepth(  ), {val: name});
  toolbar.btns.backBtn.create("back", "spring");
  toolbar.btns.backBtn.setOnSelect("moveBack", _root);
  toolbar.btns.forwardBtn.create("forward", "spring");
  toolbar.btns.forwardBtn.setOnSelect("bringForward", _root);

  // Add the two buttons to the table.
  t.addRow(new TableRow(0, new TableColumn(0, toolbar.btns.backBtn)));
  t.addRow(new TableRow(0, new TableColumn(0, toolbar.btns.forwardBtn)));

  // Add the color selector to the toolbar and to the table. Then render the table.
  toolbar.attachMovie("ColorSelectorSymbol", "colSelect", toolbar.getNewDepth(  ));
  t.addRow(new TableRow(0, new TableColumn(0, toolbar.colSelect)));
  t.render(true);
}

// This function is called when one of the first six tools is selected. 
// The function loops through the toolbarBtns array elements and deselects 
// any other buttons in case they are selected. It also sets the selectedTool 
// variable to the current tool.
function onSelectTool (cmpt) {
  for (var i = 0; i < toolbarBtns.length; i++) {
    if (toolbarBtns[i] != cmpt) {
      toolbarBtns[i].toggleDeselect(  );
    }
  }
  _root.selectedTool = cmpt.val;
}

// bringForward(  ) moves the selected shape or text forward by one depth.
function bringForward (  ) {

  // Loop through the existing shapes to find the index of the selected shape.
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] == selectedShape) {
      break;
    }
  }

  // If the shape is already at the front, then exit the function.
  if (i == shapes.length - 1) {
    return;
  }

  // Swap the depths and positions of the shape (or text unit) with the shape (or
  // text unit) that has a index within the array that is one greater than the
  // selected shape (or text unit).
  selectedShape.swapDepths(shapes[i + 1]);
  shapes[i] = shapes[i + 1];
  shapes[i + 1] = selectedShape;
}

// The moveBack(  ) function moves the selected shape or text back by one depth. The
// function uses the same logic as the bringForward(  ) function.
function moveBack (  ) {
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] == selectedShape) {
      break;
    }
  }
  if (i == 0) {
    return;
  }
  selectedShape.swapDepths(shapes[i - 1]);
  shapes[i] = shapes[i - 1];
  shapes[i - 1] = selectedShape;
}

// If the mouse is currently over any shapes or text units, the isMouseOverShapes(  ) 
// method returns true. Otherwise, it returns false.
function isMouseOverShapes (  ) {
  var isOver = false;

  // Loop through all the existing shapes (or text units) and use hitTest(  ) to
  // determine if the mouse is currently over any of them.
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i].hitTest(_root._xmouse, _root._ymouse, true)) {
      isOver = true;
      break;
    }
  }
  return isOver;
}

// This function deselects all the existing shapes except for the one specified by
// the keepSelectedShape parameter (if any).
function deselectShapes (keepSelectedShape) {
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] != keepSelectedShape) {
      shapes[i].deselect(  );
    }
  }
  selectedShape = keepSelectedShape;
}

// The removeSelected(  ) function removes the selected shape (or text unit) from the
// movie and from the shapes array.
function removeSelected (  ) {
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] == selectedShape) {
      shapes.splice(i, 1);
      break;
    }
  }
  selectedShape.removeMovieClip(  );
}

// When the shape or text unit is pressed, this callback function is invoked
// automatically. Check to see which tool is selected. If it is the select tool, make
// the unit draggable. If it is the fill tool, call the doFill(  ) method of the shape
// with the selected color.
function onPressShape (cmpt) {
  if (selectedTool == "select") {
    cmpt.startDrag(  );
  }
  if (selectedTool == "fill") {
    cmpt.doFill(toolbar.colSelect.getSelectedColor(  ));
  }
}

// When the shape or text unit is released, this callback function is invoked
// automatically. Make sure the unit is no longer being dragged.
function onReleaseShape (cmpt) {
  cmpt.stopDrag(  );
}

// When a shape or text unit is selected, this callback function is invoked
// automatically. Deselect any other shapes that are already selected and set the
// selected shape to current shape or text unit.
function onSelectShape (cmpt) {
  if (selectedShape != undefined) { 
    deselectShapes(cmpt);
  }
  selectedShape = cmpt;
}

// When a shape is deselected, set the selected shape to null.
function onDeselectShape (cmpt) {
  selectedShape = null;
}

// The checkIfMouseDown(  ) function continually checks to see if the mouseDown 
// variable is true. If so, and if the selected tool is a rectangle, ellipse, line,
// or text, the doShapeDraw(  ) function is called. 
function checkIfMouseDown (  ) {
  if (mouseDown) {
    switch(selectedTool) {
      case "rectangle":
      case "ellipse":
      case "line":
      case "text":
        doShapeDraw(  );
    }
  }
}

mouseCheckIntervalID = setInterval(checkIfMouseDown, 100);

// The doShapeDraw(  ) function calculates the width and height of the shape to draw
// based on the difference between the starting coordinates and the current
// coordinates of the mouse. It then calls the create(  ) method of the current shape.
function doShapeDraw (  ) {
  var w = _root._xmouse - startX;
  var h = _root._ymouse - startY;
  newShape.create(w, h, toolbar.colSelect.getSelectedColor(  ), selectedTool);
}

// The mouseListener object is a listener object applied to the mouse to listen for
// mouseDown and mouseUp events.
mouseListener = new Object(  );

mouseListener.onMouseDown = function (  ) {

  // Perform the following actions only if the mouse is not over the toolbar buttons.
  // This prevents the user from accidentally drawing shapes when trying to select
  // buttons on the toolbar.
  if (!_root.toolbar.btns.hitTest(_root._xmouse, _root._ymouse)) {

    // Set the mouseDown variable to true. This alerts the code in the
    // checkIfMouseDown(  ) function to be processed.
    _root.mouseDown = true;

    // Set the starting coordinates of the shape/text unit 
    // to the current mouse position.
    _root.startX = _root._xmouse;
    _root.startY = _root._ymouse;

    // Get a new unique depth and name of the shape or text unit.
    var uniqueVal = _root.getNewDepth(  );

    // Perform the correct actions depending on the selected tool.
    switch (_root.selectedTool) {
      case "select":
        // If the mouse is clicked, but not over an existing shape/text unit,
        // deselect any selected units and set selectedShape to null.
        if (!_root.isMouseOverShapes(  )) {
          _root.deSelectShapes(  );
          root.selectedShape = null;
        }
        break;
      case "line":
      case "rectangle":
      case "ellipse":
        // If the line, rectangle, or ellipse tools are selected, create a new shape
        // component instance and set the callback functions for each.
        _root.newShape = _root.attachMovie("ShapeSymbol", "shape" + uniqueVal,
                          uniqueVal, {_x: _root.startX, _y: _root.startY});
        _root.newShape.setOnPress("onPressShape", _root);
        _root.newShape.setOnRelease("onReleaseShape", _root);
        _root.newShape.setOnSelect("onSelectShape", _root);
        _root.newShape.setOnDeselect("onDeselectShape", _root);
        _root.shapes.push(_root.newShape);
        break;
      case "text":
        // If the text tool is selected, create a new text component and set the
        // callback functions for each.
        _root.newShape = _root.attachMovie("TextSymbol", "shape" + uniqueVal, 
                          uniqueVal, {_x: _root.startX, _y: _root.startY});
        _root.newShape.setOnPress("onPressShape", _root);
        _root.newShape.setOnRelease("onReleaseShape", _root);
        _root.newShape.setOnSelect("onSelectShape", _root);
        _root.newShape.setOnDeselect("onDeselectShape", _root);
        _root.shapes.push(_root.newShape);
    }
  }
};

// When the mouse is released, set mouseDown to false so the checkIfMouseDown(  ) code
// stops executing.
mouseListener.onMouseUp = function (  ) {
  _root.mouseDown = false;
};

Mouse.addListener(mouseListener);

// The key listener checks to see if the delete key is pressed. If so, call the
// removeSelected(  ) function. Note: The delete key has a special meaning when you use
// the "Test Movie" feature in Flash. If you want to test your movie in this way,
// make sure you select the Disable Keyboard Shortcuts option from the Control menu
// in the Test Player.
keyListener = new Object(  );
keyListener.onKeyDown = function (  ) {
  if (Key.getCode(  ) == Key.DELETEKEY) {
    _root.removeSelected(  );
  }
};

Key.addListener(keyListener);

// Call the init(  ) function to get everything started.
init(  );

The main routine of the Flash Paint application is a long one, but don't let that intimidate you. It really is not that scary. Let's take a closer look at some of the code.

The init( ) function is pretty straightforward. The selectedShape variable is used throughout the application to keep track of the shape (or text) unit that is currently selected. Because no shape is selected (or yet created) at the beginning of the application, initialize this variable to null. Likewise, the selectedTool variable is used to keep track of the tool that is selected. The shapes array is used to keep track of which shapes have been created, and the relative depths of each of them.

function init(  ) {
  selectedShape = null;
  selectedTool = null;
  shapes = new Array(  );
  makeTools(  );
}

The makeTools( ) function creates all the toolbar elements. There are two types of buttons: "stick" buttons and "spring" buttons. The stick buttons need to be associated with one another because only one can be selected at a time. Therefore, create an array into which the buttons are stored:

toolbarBtns = new Array(  );

To create the stick buttons, for each element of the toolbarBtnNames array the function creates a toolbar button component instance and attaches it to the toolbar.btns movie clip. Using the toolbarBtnNames array is not essential, but it is a convenient way to create the component instances. By using the array of names, you can create all the component instances within a single for statement instead of having to type the attachMovie( ), create( ), setOnSelect( ), push( ), and addRow( ) methods separately for each toolbar button:

for (var i = 0; i < toolbarBtnNames.length; i++) {
  name = toolbarBtnNames[i];
  btn = toolbar.btns.attachMovie("ToolbarButtonSymbol", 
                                  name + "Btn", 
                                  toolbar.getNewDepth(  ), 
                                  {val: name});
  btn.create(name, "stick");
  btn.setOnSelect("onSelectTool", _root);
  toolbarBtns.push(btn);
  t.addRow(new TableRow(0, new TableColumn(0, btn)));
}

Next, we want to create the Back and Forward buttons. Each of these buttons are spring buttons, and they invoke different callback functions from the stick buttons, so we create these instances separately from the stick buttons:

toolbar.btns.attachMovie("ToolbarButtonSymbol", "backBtn", 
                          toolbar.getNewDepth(  ), {val: name});
toolbar.btns.attachMovie("ToolbarButtonSymbol", "forwardBtn",
                          toolbar.getNewDepth(  ), {val: name});
toolbar.btns.backBtn.create("back", "spring");
toolbar.btns.backBtn.setOnSelect("moveBack", _root);
toolbar.btns.forwardBtn.create("forward", "spring");
toolbar.btns.forwardBtn.setOnSelect("bringForward", _root);

The bringForward( ) and moveBack( ) functions are almost identical. The difference is in which shapes are swapped. All the shapes and text units are stored in the shapes array in the order of their depths. The higher the index of an element in the shapes array, the higher it appears in the stacking order. Therefore, to bring a shape forward, you should swap its depth with the element in the shapes array that has an index that is one higher. Then, to keep the order of the elements in the shapes array corresponding to the order of depths, also switch the positions of the two elements in the array. The process for the moveBack( ) function works the same, except the shape swaps depths with the element that has an index of one less.

function bringForward(  ) {
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] == selectedShape) {
      break;
    }
  }
  if (i == shapes.length - 1) {
    return;
  }
  selectedShape.swapDepths(shapes[i + 1]);
  shapes[i] = shapes[i + 1];
  shapes[i + 1] = selectedShape;
}

function moveBack(  ) {
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] == selectedShape) {
      break;
    }
  }
  if (i == 0) {
    return;
  }
  selectedShape.swapDepths(shapes[i - 1]);
  shapes[i] = shapes[i - 1];
  shapes[i - 1] = selectedShape;
}

The removeSelected( ) function removes the selected shape or text unit from the movie, with the removeMovieClip( ) method, and it updates the shapes array so that the array contains existing shapes only. Therefore, the function loops through all of the elements of the shapes array until it finds the selected shape. It then calls the splice( ) method to remove that item from the array.

function removeSelected(  ) {
  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] == selectedShape) {
      shapes.splice(i, 1);
      break;
    }
  }
  selectedShape.removeMovieClip(  );
}

The checkIfMouseDown( ) interval function continually checks to see if the mouseDown variable is true. (The variable is set to true in the onMouseDown( ) method applied to the mouse listener.) Then, if the selected tool is a rectangle, ellipse, line, or text, the application draws a shape or text unit using the doShapeDraw( ) function. As long as the mouse is down, the mouseDown variable is set to true. Therefore, the doShapeDraw( ) method is called continuously to draw, and redraw, a shape.

function checkIfMouseDown (  ) {
  if (mouseDown) {
    switch(selectedTool) {
      case "rectangle":
      case "ellipse":
      case "line":
      case "text":
        doShapeDraw(  );
    }
  }
}

mouseCheckIntervalID = setInterval(checkIfMouseDown, 100);

The doShapeDraw( ) method calls the create( ) method of the newly created shape (or text) unit. The width and height are calculated based on the x and y coordinates of the mouse when it was first clicked and the current x and y coordinates of the mouse. Therefore, when the user presses the mouse button, holds it, and drags the mouse, the width and height change. The create( ) method is also supplied with the color value that is selected from the color selector and the name of the current tool. The name of the tool is either "line", "rectangle", "ellipse", or "text". The ShapeClass component class's create( ) method uses this value to determine which shape to draw. The TextClass component class's create( ) method does not need this parameter, so it is extraneous when the new shape happens to be a text unit.

function doShapeDraw (  ) {
  var w = _root._xmouse - startX;
  var h = _root._ymouse - startY;
  newShape.create(w, h, toolbar.colSelect.getSelectedColor(  ), selectedTool);
}

The onMouseDown( ) method of the mouse listener does several very important things. First, the method sets the mouseDown variable to true, which activates the checkIfMouseDown( ) code that starts calling doShapeDraw( ):

_root.mouseDown = true;

The onMouseDown( ) method also sets the startX and startY variables to the position of the mouse at the time the mouse button is pressed:

_root.startX = _root._xmouse;
_root.startY = _root._ymouse;

Next, the method determines the appropriate course of action depending on the selected tool. If the selected tool is a line, rectangle, ellipse, or text, the method creates a new component instance positioned at the mouse pointer and adds that component to the shapes array. The new component instance is assigned to the newShape variable so that regardless of the instance name, the doShapeDraw( ) method always calls the create( ) method from the newly created component instance.

switch (_root.selectedTool) {
  case "select":
    if (!_root.isMouseOverShapes(  )) {
      _root.deSelectShapes(  );
      root.selectedShape = null;
    }
    break;
  case "line":
  case "rectangle":
  case "ellipse":
    _root.newShape = _root.attachMovie("ShapeSymbol", "shape" + uniqueVal,
                      uniqueVal, {_x: _root.startX, _y: _root.startY});
    _root.newShape.setOnPress("onPressShape", _root);
    _root.newShape.setOnRelease("onReleaseShape", _root);
    _root.newShape.setOnSelect("onSelectShape", _root);
    _root.newShape.setOnDeselect("onDeselectShape", _root);
    _root.shapes.push(_root.newShape);
    break;
  case "text":
    _root.newShape = _root.attachMovie("TextSymbol", "shape" + uniqueVal, 
                      uniqueVal, {_x: _root.startX, _y: _root.startY});
    _root.newShape.setOnPress("onPressShape", _root);
    _root.newShape.setOnRelease("onReleaseShape", _root);
    _root.newShape.setOnSelect("onSelectShape", _root);
    _root.newShape.setOnDeselect("onDeselectShape", _root);
    _root.shapes.push(_root.newShape);

}
    [ Team LiB ] Previous Section Next Section