A simple discrete block-based drawing canvas

This example shows how a matrix could be used to hold the drawing state of a window.

The canvas in this example is an integer matrix that the user can ‘draw’ upon by using the mouse.

Controls

  • Press and hold the left mouse button to draw with black ink
  • Press and hold the right mouse button to draw with white ink
  • Press SPACE to clear the canvas
  • Press ESC to quit
library(grid)
library(eventloop)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up the global variables which store the state of the world
#  N       size of grid
#  canvas  the actual canvas (an integer matrix)
#  pen     the current pen state. Use 'NA' to indicate "not drawing"
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
N      <- 8
canvas <- matrix(1L, N, N)
pen    <- NA_integer_


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' The main 'draw' function called within the eventloop
#'
#' @param event The event from the graphics device. Is NULL when no event
#'        occurred.  Otherwise has `type` element set to:
#'        `event$type = 'mouse_down'` 
#'               - an event in which a mouse button was pressed
#'               - `event$button` gives the index of the button
#'        `event$type = 'mouse_up'`   
#'               - a mouse button was released
#'        `event$type = 'mouse_move'`   
#'               - mouse was moved 
#'        `event$type = 'key_press'`  
#'               - a key was pressed
#'               - `event$str` String describing which key was pressed. 
#'                  See \code{grDevices::setGraphicsEventHandlers} for more information.
#' @param mouse_x,mouse_y current location of mouse within window in normalised 
#'        coordinates in the range [0, 1]. If mouse is 
#'        not within window, this will be set to the last available coordinates
#' @param frame_num Current frame number (integer)
#' @param fps_actual,fps_target the curent framerate and the framerate specified
#'        by the user
#' @param dev_width,dev_height the width and height of the output device. Note:
#'        this does not cope well if you resize the window
#' @param ... any extra arguments ignored
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
draw <- function(event, mouse_x, mouse_y, ...) {
  
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # Process events
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  if (!is.null(event)) {
    if (event$type == 'mouse_down') {
      if (event$button == 0) {
        pen <<- 0L
      } else if (event$button == 2) {
        pen <<- 1L
      }
    } else if (event$type == 'mouse_up') {
      pen <<- NA_integer_
    }
    
    if (event$type == 'key_press' && event$str == ' ') {
      canvas <<- matrix(1L, N, N)
      grid::grid.raster(canvas, interpolate = FALSE)
    }
  }
  
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # If the pen is currently active, then draw on the canvas and display
  # the latest version.
  # Note that graphics coordiates are from bottom-left of screen, while
  # matrix coordinates are from top-left.  So the y-axis must be inverted
  # to set a matrix location from a mouse position
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  if (!is.na(pen)) {
    col <-       round(mouse_x * N + 0.5)
    row <- N+1 - round(mouse_y * N + 0.5)
    
    canvas[row, col] <<- pen
    grid::grid.raster(canvas, interpolate = FALSE)
  }
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Start the event loop. Press ESC to quit
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eventloop::run_loop(draw, double_buffer = FALSE)

Since an interactive window cannot be captured in a vignette, a video screen capture has been taken of the window and included below.