Canvas 2D Web Apps/Static Buttons

This chapter discusses the most basic way to implement buttons with cui2d. It is certainly not the only way to implement buttons, but it is very flexible and the code to define the appearance and function of a button is a simple if statement that checks whether the user event has been occurred in the region of the button and specifies how to react to such events. In this chapter, the appearance of buttons is static; buttons that change their appearance based on user events (e.g., a hovering mouse pointer) are discussed in the chapter on responsive buttons.

An Example: Selecting a Color

edit

The example of this chapter shows three buttons labeled “red,” “green” and “blue,” as well as a color patch, which changes its color according to the clicked button. The buttons are rendered with a bitmap image which allows for arbitrary button designs. The example is available online, and a version that can be downloaded to mobile devices is also available online. If you want to try out the example on your local computer, you should also download the files cui2d.js and the image selected.png to the same directory as the HTML file.

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
    <meta name="viewport"
      content="width=device-width, initial-scale=1.0, user-scalable=no">

    

    <script>
      function init() {
        // get image
        imageFocusedButton.src = "selected.png";
        imageFocusedButton.onload = cuiRepaint;

        // deactivate single-finger dragging for myPage
        myPage.interactionBits = cuiConstants.isTransformableWithTwoFingers;

        // set defaults for all pages
        cuiBackgroundFillStyle = "#000000";
        cuiDefaultFont = "bold 20px Helvetica, sans-serif";
        cuiDefaultFillStyle = "#FFFFFF";

        // initialize cui2d and start with myPage
        cuiInit(myPage);
      }

      // create an image for the button
      var imageFocusedButton = new Image();

      // create a color
      var myColor = "#000000"; 

      // create a new page of size 400x300 and attach myPageProcess
      var myPage = new cuiPage(400, 300, myPageProcess);

      // a function to repaint the canvas and return false (if null == event) 
      // or to process user events (if null != event) and return true
      // if the event has been processed
      function myPageProcess(event) {

        // draw and react to buttons
        if (cuiIsInsideRectangle(event, 50, 50, 80, 50, "red", imageFocusedButton)) {
          if ("mouseup" == event.type || "touchend" == event.type) {
            myColor = "#FF0000";
            cuiRepaint(); 
            return true;
          }
        } 
        if (cuiIsInsideRectangle(event, 150, 50, 80, 50, "green", imageFocusedButton)) {
          if ("mouseup" == event.type || "touchend" == event.type) {
            myColor = "#00FF00";
            cuiRepaint(); 
            return true;
          }
        } 
        if (cuiIsInsideRectangle(event, 250, 50, 80, 50, "blue", imageFocusedButton)) {
          if ("mouseup" == event.type || "touchend" == event.type) {
            myColor = "#0000FF";
            cuiRepaint(); 
            return true;
          }
        } 

        // click on background?
        if (null != event) {
          if ("mouseup" == event.type || "touchend" == event.type) { 
            myColor = "#000000";
            cuiRepaint(); 
            return true;
          }
        }
        
        // repaint this page?
        if (null == event) { 
          // draw color box 
          cuiContext.fillStyle = myColor; 
          cuiContext.fillRect(150, 150, 80, 80);

          // background
          cuiContext.fillStyle = "#404040";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }

        return false; // event has not been processed 
      }
    </script>
  </head>
 
  <body bgcolor="#000000" onload="init()" 
    style="-webkit-user-drag:none; -webkit-user-select:none; ">
    <span style="color:white;">A canvas element cannot be displayed.</span>
  </body>
</html>


Discussion

edit

This discussion assumes that you are familiar with the code example discussed in Chapter Getting Started with the Framework.

The example uses a bitmap image to draw a button. The image is assigned to the global variable imageFocusedButton:

      // create an image for the button
      var imageFocusedButton = new Image();

The image file is then specified in the init() function:

        // get image
        imageFocusedButton.src = "selected.png";
        imageFocusedButton.onload = cuiRepaint;

By setting onload to cuiRepaint, we can make sure that the canvas is repainted in case it has been rendered before the image was completely loaded.

The example defines a global variable myColor to store the user-selected color:

      // create a color
      var myColor = "#000000";

Since the example uses clicks onto the background to set myColor to black, these “one-finger” events should not be processed by the page. Thus, the example deactivates one-finger dragging by only allowing two-finger gestures for this page:

        // deactivate single-finger dragging for myPage
        myPage.interactionBits = cuiConstants.isTransformableWithTwoFingers;

For repainting the buttons and processing clicks on the buttons, the example uses three calls to cuiIsInsideRectangle():

        // draw and react to buttons
        if (cuiIsInsideRectangle(event, 50, 50, 80, 50, "red", imageFocusedButton)) {
          if ("mouseup" == event.type || "touchend" == event.type) {
            myColor = "#FF0000";
            cuiRepaint(); 
            return true;
          }
        } 
        if (cuiIsInsideRectangle(event, 150, 50, 80, 50, "green", imageFocusedButton)) {
          if ("mouseup" == event.type || "touchend" == event.type) {
            myColor = "#00FF00";
            cuiRepaint(); 
            return true;
          }
        } 
        if (cuiIsInsideRectangle(event, 250, 50, 80, 50, "blue", imageFocusedButton)) {
          if ("mouseup" == event.type || "touchend" == event.type) {
            myColor = "#0000FF";
            cuiRepaint(); 
            return true;
          }
        }

The definition of cuiIsInsideRectangle() is discussed below. Here, it is only important that it works similarly to a process function:

  • If event is null, cuiIsInsideRectangle() writes a string ("red", "green", or "blue") in the specified rectangle and then draws imageFocusedButton under it.
  • If event is not null, it checks whether this user event is inside the specified rectangle and returns true if it is. In this case, the type of the event is checked, and in case of mouseup and touchend events, myColor is changed to some color (depending on the button) and a repaint is requested with cuiRepaint() to make sure that the new color is used for drawing. Also, the function returns with true to indicate that the event has been processed.

The following code checks whether there was a mouseup or touchend anywhere else, and in this case it sets myColor to black:

        // click on background?
        if (null != event) {
          if ("mouseup" == event.type || "touchend" == event.type) { 
            myColor = "#000000";
            cuiRepaint(); 
            return true;
          }
        }

Lastly, the function checks whether event is null and in that case it repaints a square rectangle with the color myColor and a grey background:

        // repaint this page?
        if (null == event) { 
          // draw color box 
          cuiContext.fillStyle = myColor; 
          cuiContext.fillRect(150, 150, 80, 80);

          // background
          cuiContext.fillStyle = "#404040";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }

The code then returns false to indicate that the event hasn't been processed.

When you try out the example, you might realize that it feels very static even though you can change the color by clicking on the buttons and the background. The example in Chapter Responsive Buttons feels a lot more dynamic because the appearance of the buttons responds to user events.

Implementation of cuiIsInsideRectangle()

edit

The function cuiIsInsideRectangle() is defined in cui2d.js:

/** 
 * Either determine whether the event's position is inside a rectangle (if event != null) 
 * or draw an image in the rectangle with a text string on top of it (if event == null).
 */ 
function cuiIsInsideRectangle(event, x, y, width, height, text, image) {
  if (null == event) { // draw button
    if (null != text) {
      cuiContext.fillText(text, x + width / 2, y + height / 2);
    }
    if (null != image) {
      cuiContext.drawImage(image, x, y, width, height);
    } 
    return false;
  }
  else { // if (null != event) 
    if (event.eventX >= x && event.eventX < x + width &&
      event.eventY >= y && event.eventY < y + height) {
      return true;
    }
    return false;
  }
}

The implementation is very straightforward: if event is null, the text is written at the center of the rectangle unless the text is null. Similarly, image is drawn into the rectangle unless it is null. On the other hand (if event is not null), the function checks whether the coordinates eventX and eventY in event are inside the rectangle and returns the result.

Note that eventX and eventY are specific to cui2d: these coordinates are in the coordinate system of the page, which is different from any coordinate system that HTML is using and it changes if the user transforms the page with two-finger gestures.


< Canvas 2D Web Apps

Unless stated otherwise, all example source code on this page is granted to the public domain.