Humanities Home page   Office of Digital Humanities
Back     BYU LiveCode Lessons Gateway

DigHT 310
Custom Functions

Review: Built-in LiveCode Functions

As mentioned in a previous lesson, functions are little information processors included in LiveCode. You pass the function some information, and the function returns a value to you based on that input. Some functions, like the date and the time, return a value reflecting the current state of the computer’s operating system. The value a function returns changes according to circumstances; that is, when you call a LiveCode function, the value it returns is based on the information you feed it.

You may recall that the LiveCode scripting language provides many built-in functions. They can appear in two forms: the “algebraic” form, in which the arguments are enclosed in parentheses and the “prose” form with no parentheses. So function calls in this format:

  put random(15) into myRandom
  put sqrt(16) into mySqRt

are functionally equivalent to functions calls in this format:

  put the random of 15 into myRandom
  put the sqrt of 16 into mySqRt

Custom Functions

Just as you can create custom message handlers—,sometimes also called command handlers—you can also write your own, custom-made functions to do specialized calculations and data manipulation. A custom message/command handler looks like this:

on myOwnHandler
  beep
  put "I did it myself!" into fld "output"
  # do other things
end myOwnHandler

Writing a function handler is very similar to writing a message handler, except that it begins with the keyword function instead of on. It must also include a return statement as the last statement in the handler, which sends the result of the function back to the calling statement. Here is an example of a custom function. This one takes the full file path to a file as an input and returns the path to the enclosing folder.

  function enclosingFolder pFilePath
    set the itemDelimiter to "/"
    delete last item of pFilePath
    return pFilePath
  end enclosingFolder

Custom functions work just like built-in ones, except that you can only use the “algebraic” form (using parentheses), not the “prose” form, which is allowed only for built-in functions. Here is how you could use this custom function. If I wanted to get the parent folder for a specific file on my hard drive, I would first create the custom function above, enclosingFolder, in my stack script, then call it from any handler in that stack, like this:

  answer file "Choose a file:"
  put it into tFilePath
  put enclosingFolder(tFilePath) into tFolderPath

When to Create Custom Functions

It is theoretically possible to do everything in message/command handlers, but there are times when writing a custom function is more useful or more efficient. How do you decide when to use which?

Here are some rules of thumb:

When to write a message/command handler:

For example, if you are adjusting the layout of controls on a card via a script, you would most likely do this in a message/command handler.

When to write a function handler:

For example, let's say that in your program you need to calculate the distance between two points on a card. This is a problem that can easily be solved by writing a function. The function would be general and could easily used in any stack, regardless of what specific control objects the stack contained.

Anatomy of a Function Handler

Let's take a closer look at how to write a function handler. Here is the basic syntax:

function functionName parameter list
   statement list
   return return value
end functionName

Where:

Note that the return statement is normally the last statement in the function handler, since return hands script execution back to the calling handler. Nothing after the return line will be executed.

Custom Function: A Simple Example

Let’s look at a practical problem that we could solve with a well-written function. Imagine a simple fuel efficiency calculator that calculates the number of miles per gallon of fuel that a vehicle acheives. First of all what pieces of information would we need to make that calculation? Simple, right?—the number of miles traveled and the number of gallons of fuel consumed. You could write the function like this:

function mpg pTotalMiles, pGallonsFuel
   put pTotalMiles / pGallonsFuel into tMPG
   return tMPG
end mpg

Because the function is general in nature you would likely place it someplace fairly high up in the message-passing hierarchy, such as the stack script.

Once the function was created and saved in the stack script, you could call it from anywhere in the stack. For instance, you might have a button and three fields on a card. One field would hold the number of miles traveled, while the other would hold the number of gallons of fuel used. The button, of course would call the function and report the result in the third field. The buttons script might look something like this:

on mouseUp
   put mpg(fld "miles", fld "gallons") into fld "report"
end mouseUp

There are ways to improve this function. You could apply a round() function to the calculation to make the answer a little shorter. You could generalize that name of the function so it wasn't focused only on North American units of measure (although the calculation itself wouldn't change if you were using kilometers and liters instead of miles and gallons.) But this is a very simple example designed to show the format and usage of custom functions. Read on for a more complex example, for which writing a custom function might be more worthwhile.

Custom Function: A More Complex Example

The previous example was straightforward but somewhat simplistic. Let’s consider a more complicated problem. Say you have a need in your stack to calculate the distance between two points on your card. The calculation is based on the Pythagorean theorem for calculating the length of the hypotenuse of a right triangle, a2 + b2 = c2. It’s not hard, but the calculation is long enough that you wouldn’t want to type it out each time you need to do it. This is a prime candidate for a custom function.

To make this calculation you would need to send two x,y coordinates to the function. Here is what it might look like:

# The function call, in a button script:
on mouseUp
   put the location of graphic "dot1" into tPoint1
   put the location of graphic "dot2" into tPoint2
   put distance(tPoint1,tPoint2) into tDist
end mouseUp

# The function handler, probably in stack script
function distance pPoint1, pPoint2
    put item 2 of pPoint1 - item 2 of pPoint2 into dy
    put item 1 of  pPoint1 - item 1 of pPoint2 into dx
    put sqrt((dy^2) + (dx^2)) into tDistance
    return tDistance
end distance 

Creating a Custom Function: A Case Study

Let’s look at another practical problem that we could solve with a well-written function. In an older DigHT assignment that is no longer used Web-based Content Assignment students were to devise a way to resize the graphics being displayed so that they would fit into the available space on the card. This means that for each referenced image you display, you need to determine how much it would need to be resized in order to fit. The process is similar for each image and can be solved using a generalized algorithm (a set of well-defined steps)—in other words, this is a perfect candidate for a function.

The algorithm for proportionally resizing an image—or indeed any rectangular object—is straightforward:

  1. Get the "native" height and width of the image. (Look up the formattedHeight and the formattedWidth in the LiveCode dictionary.)
  2. Decide on the resize factor. For example, you may want the image to be 2 times the original size, one-third the original size, or half the original size.
  3. Change both the height and the width by multiplying both by the factor:

    newHeight = origHeight X resizeFactor
    newWidth = origWidth X resizeFactor

    In LiveCode the code would look like this:

    put the formattedWidth of image "mypic.jpg" into nativeWidth
    put the formattedHeight of image "mypic.jpg" into nativeHeight
    put 2 into resizeFactor -- assuming you want to enlarge the image by 2X
    set the width of image "mypic.jpg" to nativeWidth * resizeFactor
    set the height of image "mypic.jpg" to nativeHeight * resizeFactor
rectangle graphic showing dimensions

We’re part of the way there. In the example above we arbitrarily chose a resize factor—a bad practice if you want to write flexible code. What we need instead is a way to figure out the ideal dimensions, height and width, that we want to constrain the image to. One way to do this is to create a rectangle graphic and size it to exactly the maximum height and width that no image should ever exceed. Then we can calculate how much the image would have to grow or shrink to fit within that area.

In the illustration at right the image is both too wide and too tall to fit within the maximum dimensions outlined by the graphic "maxDim". We’ll work first with the width. By dividing the width of the graphic by the width of the image we can come up with the resize factor. Then we simply use that factor in our algorithm:


put the formattedWidth of image "windmills.jpg" into nativeWidth
put the formattedHeight of image "windmills.jpg" into nativeHeight
put the width of grc "maxDim" / nativeWidth into resizeFactor
set the width of image "windmills.jpg" to nativeWidth * resizeFactor
set the height of image "windmills.jpg" to nativeHeight * resizeFactor

Now, we could just insert this algorithm into our code, and then repeat it with slight variations to adjust the height, as needed. But we want to create a function that will be more useful in a general sense. So imagine a function that would take as input parameters the image dimensions and the maximum dimensions and return the new dimensions for both the height and the width of the image object. It might look something like this:

on mouseUp
  put the formattedWidth of image "windmills.jpg" into nativeWdth
  put the formattedHeight of image "windmills.jpg" into nativeHgt
  put the width of graphic "maxDim" into maxWdth
  put the height of graphic "maxDim" into maxHgt
  # here is the function call
  put constrainedDimensions(nativeWdth,nativeHgt,maxWdth,maxHgt) into newDimensions
  set the width of image "windmills.jpg" to item 1 of newDimensions
  set the height of image "windmills.jpg" to item 2 of newDimensions
  set the loc of image "windmills.jpg" to the loc of graphic "maxDim"
end mouseUp

## this function handler would probably be in the stack or card script
function constrainedDimensions oldW,oldH,maxW,maxH
  # check to see if width is different from the max width
  if oldW <> maxW then
    put maxW / oldW into resizeFactor
    put oldW * resizeFactor into newW
    put oldH * resizeFactor into newH
  end if
  # now check to see if the height is still too large
  if newH > maxH then
    put maxH / newH into resizeFactor
    put newW * resizeFactor into newW
    put newH * resizeFactor into newH
  end if
  # return the new dimensions
  return newW,newH
end constrainedDimensions

While at first glance this may appear a little more complicated than the original code, notice that the new function is written in such a way that you could use it to proportionally resize any object to fit into an ideal-sized area. For example, if I wanted to fit a field into the area defined by graphic "maxDim", while still retaining the field's original height to width ratio, all I would have to do is to call the same function:

put constrainedDimensions(the width of fld "myFld",the height of fld "myFld",the width of grc "maxDim",the height of grc "maxDim") \
   into newDims
set the width of fld "myFld" to item 1 of newDims
set the height of fld "myFld" to item 2 of newDims

A well-written function handler can save you work in the long run by allowing you to reuse code in different settings.

Final Word

Used properly, functions are a great way to save yourself work by placing commonly-used calculations into a function handler. A well-written function should be abstract, generally-applicable, and placed high enough up in the message hierarchy that it can be accessed from any object that may need access to it.


Functions Exercise

1. Complete the Functions Exercise as outlined in the FunctionsExTemplate.rev stackfile (at http://dight310.byu.edu/lesson_materials/03_functions/FunctionsExTemplate.rev). Download your own copy from the Templates folder and rename it so your name is on the file.

2. Read LiveCode developer Geoff Canyon's article about functions. (Geoff's article was written several years ago, when LiveCode was called "Revolution".) While I don't agree with his ideas in every detail, it is an excellent discussion of how to write good functions and when to use functions rather than handlers. At the end of the article is a section called Postscript -- Examples where he gives some ideas for cases in which you might want to write a function. Take the third example, Quoting Text, and write a function in your Functions Exercise stack that will do what the example describes. On the last card at the end of your Functions Exercise stack create a demonstration of the use of this new function.

Turn it in to the homework drop folder when you're done. As always, the key to this exericse is in the Keys folder for your reference if you get stuck.


Back     BYU LiveCode Lessons Gateway
Maintained by Devin Asay.
Copyright © 2005 Brigham Young University.
This page last updated on January 15, 2020 15:56:02.