HyperActive Software

Home What's New Who We Are What We Do Solutions Resources

We make software for humans. Custom Mac, Windows, iOS and Android solutions in HyperCard, MetaCard, and RunRev LiveCode


Polling the Mouse in MetaCard and LiveCode

(or) How to wreck perfectly good software

by Jacqueline Landman Gay
HyperActive Software

A mouse() on a hamster wheel

A common scripting technique in any x-talk language is polling the mouse in order to get information about its state so that a script can perform an action. In HyperCard or SuperCard, for example, it is customary when simulating a dragging operation to use a repeat loop to get the mouseLoc in order to repeatedly set an object to the mouse location, or to check in a loop for a mouse condition to determine when to leave the loop. Here is a common example:
repeat until the mouse is up
  set the loc of btn "dragBtn" to the mouseLoc
end repeat

In MetaCard and LiveCode, this kind of mouse polling is emphatically discouraged. Scott Raney of MetaCard Corporation has this to say about the above repeat construct:

This loop uses 100% of the CPU time, regardless of the speed of the processor, bringing the system to its knees, causing poor feedback for your app, and making your system unresponsive to any other processes running on it. You see, on a fast system, 99.99% of the time through that loop you're setting the loc to exactly the same coordinate!

I think the biggest problem here is that OSs have evolved way beyond the point where even developers (or at least most of our customers) understand how they work. There's nothing wrong at all with things like "the mouse" on single-user single-tasking OSs like MacOS, but that's just not the world we live in anymore...

I'm certainly sympathetic to the "easier way" and "programming for the rest of us" arguments, but none of us can afford to be classed as developers of system-hostile applications... Developing in a high level language will become a stigma that we'll never live down...

Some of the processes that can slow down or stop when a script uses this kind of processor-intensive repeat loop are: file and printer sharing, HTTP/FTP servers, network management tools, and on UNIX systems (including Mac OS X), people telnetting in from other systems. Another problem on Mac OS X is the spinning rainbow cursor that may appear while the loop is in progress if the operation takes too long — the system puts that up whenever it thinks an application is unresponsive. That means if a user drags an object around the card for a while, or keeps the mouse button depressed for too long while the script is in a loop, the system may think your application has gone dead.

The problem is not limited to just checking the mouseloc or the mouse's up or down state. Checking any of the following mouse events within a loop can cause a bottleneck and should be avoided:

  • the mouse -- i.e., "up" or "down"
  • the mouseClick
  • the mouseH
  • the mouseV
  • the mouseLoc

So what do I do?

You may be wondering at this point, "If I shouldn't be using a repeat loop to determine the state of the mouse, what do I use?"

The answer lies in the mouseMove system message, sent by the MetaCard/LiveCode engine whenever the user moves the mouse. The mouseMove message carries two parameters with it: the horizontal and vertical coordinates of the current mouse position. By writing a handler that traps this system message, you can create an OS-friendly script that will not bottleneck the CPU and which will run far more efficiently than any repeat loop.

There is an additional advantage to using the mouseMove approach: it allows your stack to continue sending events and reporting other user actions simultaneously. Unlike a repeat loop, which locks out everything else until the handler ends, trapping a system message takes an insignificant amount of time and allows other system messages and all your stack's features to continue operating at the same time. You may not always want to use the other messages that are being sent, but they are there if you need them.

How it works

In the above example, all we want to do is move a button to the same location as the current mouse position, for as long as the mouse button is depressed. If the mouse is "up", the script should do nothing. A short handler in the button script could take care of it:

on mouseMove x,y
  if the mouse is down then set the loc of me to x,y
end mouseMove

Very easy. At the same time that mouseMove is catching the position of the mouse and handling the drag, you can also have other handlers running normally to catch various system messages or handle other events. You can think of mouseMove as a sort of customized, single-purpose idle message. (Note though that idle has its own set of processor-hogging problems, and should be avoided at all costs too.)

But wait. The mouseMove handler is still polling the mouse, checking for its down state repeatedly. So while we have improved the script's efficiency, it is still using more resources than necessary. We can fix that by adding a variable that stores the state of the mouse, so that the engine won't have to check it each time. We can set a flag on mouseDown, and turn it off on mouseRelease and mouseUp. While we're at it, we'll also add a line to the script that passes the ID of the object we are dragging so that more than one button can respond to the mouseMove message.

These handlers would go in the card or stack script:

on mouseDown
  global gDragging,gDragObj,gDeltaX,gDeltaY -- (you could use local script variables instead)
  if the name of the target contains "button" then
    put true into gDragging
    put the long id of the target into gDragObj
    -- the following adjusts for the mouse position on the object;
    -- it isn't part of what we're explaining here but it enhances the drag:
    put item 1 of the loc of gDragObj - the mouseH into gDeltaX
    put item 2 of the loc of gDragObj - the mouseV into gDeltaY
  end if
end mouseDown

on mouseRelease
  global gDragging
  put false into gDragging
end mouseRelease

on mouseUp
  global gDragging
  put false into gDragging
end mouseUp

on mouseMove x,y
  global gDragging,gDragObj,gDeltaX,gDeltaY
  if gDragging = true then
    add gDeltaX to x
    add gDeltaY to y
    set the loc of gDragObj to x,y
  end if
end mouseMove

Most HyperCard and SuperCard scripters are familiar with mouseDown and mouseUp, but mouseRelease may be new. The mouseRelease message is sent to an object when the mouse button goes up but the mouse is no longer within the borders of the object that received the original mouseDown message. This covers the case where the user may move the mouse faster than the button can keep up. Even if the mouse is released somewhere outside the button, this script will still set the global gDragging to false.

Now you've got a fast, efficient, CPU-friendly set of handlers that won't bog down the OS or hog system resources. Applications running in the background will still get their fair share of CPU cycles with no performance hit. Your stack will respond much faster and will allow other user actions and scripts to be handled simultaneously. There's no down side, there's lots to gain, and your stack will run like a pro.