NEWS, EDITORIALS, REFERENCE

Subscribe to C64OS.com with your favorite RSS Reader
April 19, 2018#61 Programming Practice

Double Click and Timers

Post Archive Icon

This is a very short, practical, follow-up to my post earlier in the week about my final direction with how to implement timers in C64 OS.

I implemented the input module a long time ago, it was one of the very first modules of code I wrote for C64 OS. It is sort of a combination of mouse and keyboard driver, and maintains queues of the three broad categories of input events: Mouse Events, Keyboard Command Events and Printable Key Events. You can read all about this, from my post The Event Model, which was published in January 2017.

The mouse events consist of mostly mouse primitives, this excludes drag and drop events, for example. It also excludes boundary crossing events such as mouseover and mouseout. So the events that it supports are:

  • move
  • left down
  • left tracking
  • left up
  • right down
  • right up

Additionally, I considered to be of sufficient primitiveness, left click, left double click and right click. These are ever so slightly more than purely primitive, because a click is a not a state of a button, but rather it is a sequence of button states with a temporal aspect. If you mouse down and then later mouse up, but too much time has passed, a click will not be generated. In order to really get it right, the whole thing turned out to be more complicated then I was anticipating.

 

Let me quote my earlier post:

It is not a surprise to me why most mouse routines in other C64 projects have limited support and what one would consider odd behavior by modern standards. It's really hard to get it right.

When no buttons are pressed, and the left button goes down, a left down event is generated. […] While the left button is held down pressing the right button is completely ignored. […] When the left button is finally released a left up event is generated. If a sufficiently short period of time has elapsed between when the left button went down and when it went up, a left click event will also be generated.

A left double click event will be generated if a sufficiently short length of time elapses between two click events, however I have not implemented double clicking yet. Managing the buttons is quite tricky. Greg Naçu — The Event Model — January 2017

Measuring the delay between mouse down and mouse up was originally implemented as kind of a hack. but I needed to do something because the click event type, despite being less primitive than up or down, is actually one of the most common event types around which to write any code. GEOS tends to want to trigger behaviours on mouse down, and this can occasionally make a UI feel more responsive. However, you lose the ability to click down, suddenly change your mind, and drag the mouse away before releasing in order to prevent the full click from occuring. It's subtle, but supporting proper full click events is important to me in making the UI behave and feel more modern.

If click is slightly less primitive than up and down, then double click is one more step removed again. It too contains a timed aspect, but it's a delay not between primitives but between two clicks. This makes it surprisingly more difficult. On the other hand, double clicking is a much less common event than single clicking, so I just left double clicking unimplemented.

…Until now.


I've recently been working on implementing "Utilities", which is the C64 OS analog to "Desk Accessories" either from the classic Mac era or from GEOS. C64 OS's Utilities will be far less modal than Desk Accessories are in GEOS, and more universally accessible. And also will be more integral and necessary to the basic functioning of almost every app. I'll write about these in more detail in an future post.

A couple of months ago I tweeted out a cool lookin' mock up of a sort of floating Color Picker palette thingy. Here's that tweet:

In the meanwhile I figured out how I would actually be able to technically support these utilities. And actually implemented support for them. The first one I implemented is the color picker from my original mock up. Here's how it looks now.

New ideas came to me, for example, instead of a cancel button, why not have a close control in the drag bar at the top of the panel? (And do you really need a label "Color Picker:" to know what this thing is? Gone.) Instead there's a Choose button at the bottom.

If you single click a color option, it selects and becomes highlighted. Then if you single click the Choose button, the command code and color code will be sent to the application (more detail on this later) and the utility will be quit automatically. It takes only a fraction of a second to load it anew from disk. So that's pretty cool.

But, wouldn't it also be cool if you could just double click on a color option and have it send that color option to the app and close the utility all in one step?!

This is actually the first place in any thing in C64 OS where I am ready to make use of double clicking. But, I hadn't implemented double clicking yet. So I decided to go back to the input module and review the code and see what it would take to finally implement double click support.

Timers to the Rescue

When I took a look at the code, it became super obvious how to implement it. Now that timers are universally available and easy to use, this particular use case is just calling out to use them.

There is a large block of code that under certain circumstances makes a JSR to a subroutine to pushevent, taking the event type itself in the accumulator. The subroutine gathers all the other relevant information about mouse position, packages it up as an event and adds it to the mouse events queue.

Left click (and right click) are not exclusive to the primitives that produce them, in the same way that a double click is. What do I mean by this? When you left down, a left-down event is immediately queued and if the main even loop is freely running, it will be delivered out to the application immediately. When you left up, similarly, a left-up event will be immediately queued. Plus, if a short enough time elapsed between those two, a left-click event will be queued just after the left-up. What this means is that, even though a down and an up are required to produce a click, the production of the click event does not exclude the production of the up and down events that preceded it.

This is not quite the case with a double click.

The production of a double click requires two clicks, however the second click event will not be produced. Instead it will be replaced by the double click event. Here's the order of events that will get queued, in the order they're queued, leading up to the production of a double click.

left-down → left-up → left-click → left-down → left-up → left-dbl-click

Here's how it works. There's a single byte variable called "didclick". In the block of code that pushes a leftclick, if didclick is 0, then it pushes the leftclick, sets didclick to 1, and immediately queues a timer that counts down 0.5 seconds. (30 Jiffies. Although, making this delay loadable from a configurable envvar is on the short list.) When the timer expires the only thing it does is set didclick back to 0. Everything else proceeds as usual. But if you do what's necessary to produce a click again, if you did it all before the timer expires, then instead of pushing a click event, it will push a doubleclick event, and then set didclick back to 0 straight away.

It works very reliably. And, backended on timers, was super easy to support.

Now I've got two different modules of system code that make use of timers.