NEWS, EDITORIALS, REFERENCE
Menu Systems, a comparison
Well, at the very least, this post has got the right number!1 Although I don't know if its content will live up to the hype of the number, I do have some fun stuff to show today. We'll be looking at the menu system in C64 OS, seeing it in action, and doing a comparison to GEOS. It's just as a friendly comparison, since GEOS is the quintessential OS-with-menus that people think about on the C64. So, how do they stack up?
The menu system was something I started designing a long time ago. In my head it was something I knew I wanted to have, and I spent a good deal of time pondering how it could all come together. Consequently, the menu system has been mentioned many times and is the subject of at least two posts from early last year. Recursion and C64 OS's Menu UI (3/3) from March 1st, 2017 when I did a technical deep dive on writing recursive code in 6502 ASM. And then again on May 9th 2017, in Pointers in Practice, Menus, which was a programming theory post about pointers and how they can be used in creating hierarchical data structures.
I'll be the first to admit that I'm apparently a slow coder. In the earliest days it was a huge hill for me to climb to understand how to do anything in assembly language. And I went through many stages of reforming the code in order to be able to sustain large project development coding natively on the C64 itself. Additionally, I've really had to educate myself about each new aspect of the computer as I've gone. Learning about how the CPU works, and the VIC modes, and how memory is mapped, and how the CIAs can be programmed, and how to use interrupts, etc. It's been quite a trip.
But it has all come to fruition. I had the menus actually functioning and doing what I wanted them to starting maybe 3 or 4 months ago. Since then I've been adjusting them, and experimenting with what I can do with them in the context of writing a real world application, or the new C64 OS utilities. This has lead to the addition of numerous small features and behavioral tweaks to get them to where they are now. I'm finally ready to show them in action, and I'm really eager to get into a technical discussion about how they work and all that you can do with them. So let's dig in.
The Menu Bar
Like many other modern nerds, I read John Gruber's Daring Fireball. Now, he's an Apple nerd, and obviously that whole world has progressed far beyond the low-level technical minutiae of computers that still intrigues us 8-bitters. However, every so often something will remind the big boys of their history and they'll reminisce about floppy disks or low-resolution screens, or early design decisions in user interface that have long faded into the background of everyday use. One such instance came earlier this year, when Gruber linked to a great piece by Jack Wellborn over at wormsandvirus.com entitled, The Menu Bar. It's relatively short you should take 5 minutes to go read it.
Reading such an article in 2018, 2 years after I'd begun my adventure into creating an OS for the C64 that has a UI that is centered around the persistence and consistency of a menu bar along the top of the screen, was very encouraging. Here's what Wellborn has to say about it:
The menu bar has been, and in my opinion remains, the best mechanism for providing familiarity, discoverability, and progressive disclosure in user interfaces on any platform. Jack Wellborn — WormsAndViruses.com, March 2018
Quite clearly, I agree.
Everything that is good about the concept of a menu bar is accentuated on a low-resolution screen. This should come as no surprise, considering that the original Macintosh had a resolution of just 512x342 pixels. Well, our trusty C64 is only 320x200, so we're even more constrained.2
A menu bar can pack in an unbelievably huge number of selectable items, without taking up any addtional screen real estate than the base menu bar strip itself. A simple program with only a few selectable items, and a massively complex app with hundreds of features, they both occupy the same tiny amount of real estate. The menu items can be grouped sensibly making them easy and intuitive to find. You don't need to remember what commands the application accepts, the menus expose the available options, making features discoverable to new users. And hierarchically nesting menus means that you are never overwhelmed by more than a handful of items at a time.
Keyboard shortcuts on menu items passively expose the user to poweruser features. You are never required to remember a keyboard shortcut. But if you find yourself repeatedly returning to the same menu over and over, not only does mousing there become tedious but you also see the shortcut appear before your eyes on each repetition, beckoning you to a faster way to perform the action.
Above and beyond the benefit that a menu bar gives to any given application, the real power comes with the consistency that a menu bar provides to the user laterally, across applications. Jack Wellborn points out that MacPaint in 1984 and MS Excel in 2018 share little in common, but they do share the menu bar. And both have a File menu, underwhich a Save option is found. The number of different ways to save your work across the vast array of C64 programs is mind boggling.
But, we knew all this already, right? That's why GEOS uses a mouse, and has a menu bar at the top of the screen, right? Absolutely. GEOS was early to pick up on the utility of a Graphical User Interface. GEOS 1.0 came out in 1985, the year following the famous release of the Macintosh in 1984.
That's all true, however, in my opinion, GEOS and its later evolution seen in Wheels, provide a rather sub-par experience of menus on a C64. There are many reasons for this, but my main goal in C64 OS is that the system feel fast, and as a consequence that it be usable. One should never under estimate the complete unusablility of something that feels slow. But what's more is that you can get a computer that is objectively a slow computer to feel fast, as long as you make certain concessions that match the speed and capabilities of the computer to the job you are trying to get it to do.
This is why, for example, in the classic Mac OS when you drag a window, only the outline of the window moves, not the whole opaque surface. The hardware just wasn't fast enough to recomposite so many pixels—in realtime—quickly enough to make the whole system feel fast.
GEOS's menus suffer from many issues that contribute to making them feel perilously slow. Somehow when I was a teenager, I found them to be totally acceptable. But now after interacting with a modern computer for almost two decades, going back to the way GEOS menus work is an exercise in utter frustration. But, the irony is that the C64 can do much better! Which is what I'm aiming for with C64 OS.
Okay, here's a video of me showing various points of interest in the GEOS/Wheels menu system. After the video, I'll go over these points.
Bitmap draw speed
There really is no other way around it. Rendering to the bitmap screen is just slow. GEOS does a good job of it, under the limitations of the hardware, but it's still slow. Now, to be fair, Wheels was written in the era when the SuperCPU was in active production, and of course, at 20Mhz the render speed of Wheels is very zippy. But we don't have the SuperCPU anymore. It's not available for purchase. And, even if it were, there are many people who feel that the SCPU is "cheating". Someone on IRC, when I expressed awe and wonder at seeing Doom or Wolfenstein 3D running on the SuperCPU, pointed out that the SuperCPU is basically a whole other computer, which makes use of the C64 as little more than a crappy graphics card.
That's harsh. But he has a point.Click-opening behavior
The source code to GEOS 2.0 is available on GitHub. It's been reverse engineered, touched up, commented, and published. That source code also includes the input drivers for: Joystick, 1351, light pen and koala pad. I've written the mouse driver for C64 OS. I've written the event model for C64 OS. I've even blogged about my experience doing so, here and here. Let me say, the most telling thing to notice about the input drivers in GEOS is how simplistic they are.
GEOS reserves a small chunk of memory where it expects to find the active input driver. Loading a new input driver consists of just loading a small chunk of code, that has been assembled to that address, into the provided space. The driver starts with a jumptable consisting of about 4 routines. The system calls those routines automatically to update the position of the cursor and read the button status of the input device. The abstraction is clever, it's quite unusual for C64 software. But the downside is that the system is geared around the lowest common denominator of input devices, and that's the directional joystick with a single fire button.
Double clicking, right clicking, clicking and dragging, button up and button down, modifier keys held in combination with mouse button events, these events are either non-existent or very primitive in GEOS. And this shows through in how you activate the menus.
You mouse down and the menu pops open, but when you mouse up nothing happens. So you require a full click to be able to move on to the next step. But the click isn't triggered on completing a click, it's triggered on mouse down. I've been web programming for over 15 years, and every website hacks together its own menu system. Quality varies greatly from website to website. In my experience GEOS's interaction feels like what an amateur would put together. In any case, it is not conducive to making the system feel fast.Feedback/Rollovers
I'm not sure, but it's probably a combination of factors that explain why the menu items have no rollovers. First, it's probably too slow to redraw the pixels in realtime. And if that's the case, then it's probably a good idea they didn't do that. Because nobody wants it to be even slower.
But it's also possible that mouse tracking in the event system just makes doing rollovers inconvenient. It's not the end of the world, but if you're used to menus on, say, the Mac, not having rollovers makes the menus feel static and unresponsive.Auto-closing on leaving menu bounds
This one is particularly aggravating. And I'm not at all sure why it works this way. But, once you pop open a menu, your mouse must remain inside the bounds of the menu or the menu will close automatically. In fact, in order to make sure your mouse is inside the bounds, when you open a menu the cursor is moved for you to be above the first item. This alone is very strange. I thought it was cool when I was young, because you could click once, click again, and you would activate the first item in the submenu. Now I just think it's weird.
It's bad when the UI punishes the user for trying to go too quickly. If you move the mouse quickly, you're bound to accidentally overshoot the edge of the menu. When you do, the menu closes and you've got to start over again. This is made worse by the slow render speed. And it's absolutely criminal when it happens when you're in a sub-sub-menu, because both submenus collapse and you've got to slowly reopen both.Horizontal Alignment
The horizontal misalignment of a submenu beneath its parent may feel like a nitpick, but it is symptomatic of trying to get a machine to do what it's not intended to. There is a good reason why the original Macintosh had a black and white screen. It may have been sub-par for arcade games, but it had the advantage that it didn't need to push color data around with its slow hardware.
The C64 shows its roots as a gaming machine with all the tricks that the VIC-II can do. But one of those tricks is the 8x8 pixel tile-based color limitations. It lets the machine show color, but it has low hardware and memory requirements by limiting those colors and to which pixels they can be applied. In a Graphical User Interface like GEOS, a major tentpole feature is having variable-width fonts. The problem is that words written in those fonts no longer line up with the tile boundaries. (I said "pixel boundary" in the video, but I misspoke, I meant to say tile boundary.) Since GEOS applications can support color, you cannot have a grey menu pop down over top of a colored background, and have the edge of the grey menu bisect a tile.
The alternative is to offset the menu slightly so its left and right edges fall precisely on the boundaries of an 8x8 tile. But then, the variable-width font in the root menu doesn't have that limitation, and so there is some discord between the two, and they end up not lining up. It's not the end of the world, it just looks janky.No submenu indicators
It is a basic principle of User Interface design that the UI should give you some indication of what is going to happen when you click it. I learned this early in my adventures as a Mac user.
When you look at the example menu above, when you see an item like, "Sort Lines…", you know that clicking it is not going to just instantaneously sort some lines. It's going to give you a dialog box of some variety either requesting confirmation or letting you specify some sorting options. How can you tell that? Because of the little dot-dot-dot. "Straighten Quotes" on the other hand, click that, and it's not going to ask you if that's okay. It'll just straighten the quotes and be done with it. Why? No little dot-dot-dot.
Well, the same is true for the parent menu indicator. In modern menu systems, first of all, the submenu automatically opens up when you roll over the parent item. That doesn't happen in GEOS. But further, even before you roll over the item, modern menus tell you they will open a submenu with little right pointing arrow. GEOS requires you to click each item, but it gives you neither kind of indication about what will happen.Duplicate of keyboard shortcuts
I'm not really sure how GEOS or Wheels looks for its keyboard shortcuts on the menu items, but it is very odd to me that two different menus, with two different entries, meaning different things, can both share the same basic shortcut. Like Commodore-E. It must be the case that the menus themselves only apply contextually, even though both appear active at the same time. It's just strange. You press Commodore-E, what will it do? Will it erase the current disk (and there is no dot-dot-dot, so, will it at least ask for confirmation first??) or will it merely delete a file?
In part, I think the reason for duplicating some shortcuts is because it is handy to keep them mnemonic. (P for Print, O for Open, C for Copy, etc.) But as far as I can tell, GEOS keyboard shortcuts never combine more than one modifier key. It is always just Commodore Key + one-other-key. This gives you only a small number of shortcuts, maybe 45 to 50, many of which will not work mnemonically. Modern OSes, in order to keep the shortcuts mnemonic, but still avoid duplicating any and the ambiguity that would lead to, will make Command-S for Save, but Save As… will use Command-Option-S. The more common of the two commands uses the simpler shortcut.
Again, my mission in life is not to just harp on GEOS. It came out a long time ago, and the fact that it can render bitmapped, variable-width fonts at all, is clearly an amazing feat. Within the limitations of the resources, their focus was on accomplishing other goals.
But, still, it's useful to have some point of comparison. So, let's move on now to C64 OS.
C64 OS, menus
The following is part 2 of the video comparison. This time I go through a number of features of C64 OS menus. Some are analagous to GEOS menus, but in other ways I go into some implementational details that are specific to C64 OS. I'll go through the features one at a time below.
The Text Screen
C64 OS renders its primary UI to the text screen. There is no hiding that fact. This doesn't mean that showing graphics, (or even video, that's a teaser) will not be possible. C64 OS has support for switching to the graphics screen, either hires or multicolor at any time. The mouse is still active when in bitmap mode, and keyboard shortcuts still work. But events and text-mode rendering are processed differently.Custom Character Set
Early on, when I first started thinking about how to use memory, and how much space I wanted available for system code, etc., I decided that I did not want to sacrifice 2K of RAM for a custom character set. And I started to play around with using existing PETSCII graphics characters to construct a sensible user interface. You can read these early thoughts, in this post from 2016, A Modern Character Set.
However, early this year, I had a complete change of heart, and decided I need to use a custom character set. I worked out changes to the memory map to make this possible and discovered to my joy and contentment that I could get huge benefits at much less expense than I'd originally worried. You can read all about this in my post from March 2018, Rethinking the Memory Map.
The custom character set doesn't only give us the ASCII characters that are missing from PETSCII, it also opens up an entire screencode block3, block 4, for creating a set of 32 custom 8x8 pixel graphics. At least four of these I've used to create 4 symmetrical arrows. And another to create a small Commodore logo. The other characters I'll leave for a discussion in another post.
Many games use character mode to get great speed, and they look very very graphical, because the entire character set is put to use as custom graphics tiles. In the context of an OS, we always need to have the complete alphabet, upper and lower case, plus numbers and the complete set of standard symbols. Nonetheless, using the last block for custom characters can add flourishes of graphics to the screen and make them look much nicer.Fast Redraw
The ultimate point of using character mode is because it is just so damned fast. Game programmers know this, that's why they use it so much. One of the stated goals of C64 OS is to work with the limitations of the hardware, to produce something usable that feels fast. Character mode is, in my opinion, the only realistic way to do that.
And fast it is. You see the video. As quickly as the mouse can be moved over a menu, it pops open. It happens so fast that you cannot see it drawing. This speed of redraw opens up several possibilities that just aren't practical in an environment where the redraw is very slow.Auto-opening and auto-closing menus
In the GEOS menus, opening a menu requires a click. And when you move from one root menu item to another, the first menu closes, but you have to click again to open the next. By comparison to the menus in a modern OS this is almost unbearably slow.
C64 OS menus work much more like modern menus. They don't work exactly like modern menus, because modern menus have very complex behavior. And everything is a tradeoff between the amount of code required to get a behavior and how useful that behavior is. C64 OS menus, at least for v1.0, behave very much like Macintosh menus from System 7 and below. For those of you who don't know, you mouse down, and the menu pops open. You hold the mouse down and drag your way through menu system, and the submenus pop open and snap shut as you pass over them. When your mouse is finally over the item you want, you release the mouse button. The item is selected, and the menus all snap shut.
Modern menus, on the Mac at least, retain this behavior and it is extremely fast and usable. In fact, I'd say it's the poweruser way of navigating the menus. Around Mac OS 8, however, in order to help less savvy users, the menus gained an additional interaction mode. You do a full click on a root menu, and a submenu pops open and stays open. Then as you roll around without the button held the menus pop out and snap shut automatically. To select the item you want, you just do a full click on that item. It's still pretty quick, but doesn't require you to hold the mouse button down the whole time, so it's a bit easier on beginners. C64 OS does not implement this more complex behavior.
There is no technical reason not to, only that it requires more code, and memory is precious on the C64. There are a few nice side effects, from an event model perspective, of only supporting the former. For example, the menu system is either active or inactive (the user is interacting with it, or is not). And the active mode only persists between a mouse down and a mouse up. So, if a mouse down event occurs, we know with certainty the menu system is not currently active. We only need to check if the event occurred in the top row of the screen. If it did not, then the event can automatically be passed to the layer below, it will not affect the menus. That's a huge code savings right there.
Alternatively, if the mouse does go down in the top row, the menu system is activated. And all subsequent events are routed to the menu system, and are never passed through to the layer below. Mouse track events update which item the mouse is over, if any. When the mouse up event finally arrives, the menu system is deactivated, and whatever the last active item is can be triggered. Believe me, it makes the event modeling much simpler. And eliminates such complications as the click-through problem.
A Quick Aside: Want to know what the click-through problem is? Here's an example, on the Mac. You click to activate a menu. Now you roll off the menu, but it is still open. Your mouse is above something below the menu layer, and you click. The menu will close, but should the click event pass through to the lower layer thing your mouse is over? The answer is not simple. On the Mac the behavior is intentionally inconsistent. If you have two windows, slightly overlapping, and the click occurs over the backmost window, the click-through will cause that back window to be pulled forward. However, if the mouse is over the close button in the corner of a window, the click-through will not occur. The menu closes, but the window does not close. The click-through is inconsistent, for reasons that some actions are more dangerous than others. Dangerous actions tend not to be triggered by click-throughs, while benign actions tend to be. But not always. It's complicated. All of this complication can be bypassed by simply not supporting that mode of interaction with the menus. Something tells me, this is the reason why System 7 only used the mode that C64 OS now uses. It's conceptually much simpler.Not constrained to the box
A closely related trait to the auto-opening and closing of menus, is that the mouse is not constrained to the bounds of the menu. In C64 OS, you can freely move the pointer off the edge of a menu and the menu stays open.
This is particularly good when you consider that the C64 OS mouse pointer has acceleration with a user configurable (though simple) curve. Read my post about mouse acceleration. You don't want to be punished for moving the mouse too quickly and overshooting the target.
When you leave the bounds of any menu, all the menus stay open, until or unless you move over a different menu parent item. In which case, all the submenus and sub-sub-menus of the previous parent close, as you would expect, and the submenu of the new parent pops open. This can get fairly sophisticated, as I tried to show in the video. You can select an item in the root menu to open a submenu, then open a sub-sub-menu, then open a sub-sub-sub-menu, and then move outside the bounds of that final menu. Everything stays open. (Thank God). If you then move your mouse back to the first submenu, but get there by passing the mouse outside the bounds of any menu, when you re-enter the first submenu, the currently open sub-sub- and sub-sub-sub- menus close, but obviously not the one your mouse is now over. It's really great.Rollover Highlight
Of all the hackneyed menu systems found throughout the web, many overuse the concept of the rollover. I blame this partly on Netscape Navigator, where back in Windows 3, every icon in its UI had some rollover effect. This was later copied by Microsoft into a core behavior of Windows95. It got to the point where a typical PC screen would blink and flicker, and invert, move and change colors constantly with the mere act of sweeping the mouse cursor from one side of the screen to the other. Mac OS X introduced more rollover effects than Classic Mac OS had had, the Dock and the window close/minimize/zoom widgets being the obvious examples. But in other ways it tried to keep the UI as unaffected by rollovers as possible, without looking outmoded.
But, menus? Even Macintosh System 1.0, and Windows 1.0 (and all 10 people who ever used that version) had a rollover effect to highlight the menu item the mouse is currently over. Check out these YouTube videos from Software Showcase:
Maybe it would just have been too slow for GEOS to invert the bits in the bitmap in realtime as you rolled over, maybe they didn't think it mattered. But, C64 OS menus have rollovers, and I personally think it really makes them come alive.Submenu Indicator
Not much to say here, only that, unlike GEOS, C64 OS menus indicate that a menu item is the parent of a submenu. Having the indicator there makes the navigation more obvious, you know there are more menu items to find, nested below an item in the current menu, before you even get down there with your mouse.
A custom character, a right pointing arrow, is used for that indicator.Submenus, Parsing and Depth
I've never programmed for GEOS, but I've read the official programmer's reference guide. Menus are statically declared in tables that are assembled into your code. The action of a given menu item is able to call the routine to display another static menu. The menus are manually sized and positioned, and my conversations in IRC with current GEOS app developers has indicated that they literally use trial and error to figure out where a menu ought to be statically positioned on the screen. That's fine, it works, it gets the job done. I suppose. But it sounds barbaric.
C64 OS menus are dynamically instantiated, in memory that is allocated at runtime, by parsing a human-readable (and human editable), text-based menu data file that is bundled together with the application's executable binary and other resources. Without needing to reassemble the app's binary, without even having access to the app's source code, a tech savvy end-user can edit a menu data file, and rearrange the complete hierarchy of the items. Can change the textual labels and assign new shortcuts. When the app loads in, parses and instantiates the menus, their size and positioning are dynamically computed. A menu is automatically made as narrow as possible, but just wide enough to accommodate the maximum combined width of label and keyboard shortcut indicators.
It's even cooler than that. Reading in and parsing the menus is part of the standard procedure of loading and running an app, but it's also just a standard system call. The app itself can programmatically ask the OS to load and initialize an alternative menu data file, at runtime. So, if your app has multiple modes, when the user switches modes, the app could load in a different set of menus custom for each mode. That is really friggen cool.
Okay, so how about nesting depth? Good question.
The whole process is recursive. But recursion has some caveats. In philosophy, or in abstract math, a recursive process is one that is limitlessly deep. That's the theory, but in practice a recursive process on a computer is constrained by how much stack space is available, and how much stack is required by each nested iteration. The menu data file parser is recursive, and it uses the 6510's hardware stack. So, if your app were already using an extraordinarily large amount of stack, and then you tried to parse a deep menu file, well, you could run out of stack. If that happens, bad things will ensue. The app could crash, maybe even the whole system, I've never tested that. But, that's fairly rare. In practice, the parser can parse to much greater depths than you'd probably ever want to go. Let's say each iteration uses 8 bytes of stack. The stack is 256 bytes big, so that gives you a maximum of (256 / 8) around 32 levels deep. I checked many many real-world, complex, Mac applications, and never found an app that uses menus more than 5 levels deep.
But that was for parsing. There is also the rendering side. How many menus are currently rendered, how to collapse them all after an item is clicked, etc. That too is a recursive process. But it doesn't use the CPU's hardware stack. It uses a custom stack that's allocated into the OS's menu module at assemble-time. This stack supports 4 menu levels, including the menu bar itself. You get the horizontal menu bar (1), a vertical submenu (2), a vertical sub-sub-menu (3), and in extreme cases you can make use of a vertical sub-sub-sub-menu (4). The menus do not scroll, so you're limited by screen real estate to 24 items per submenu. If you count up the number of possible items that could be represented with say 6 root menu entries, it's 6 x 24 x 24 x 24, or over 80,000 items. More ram than there is in a C64.Lining Things Up
It's much easier to align menus to their parent, and align everything to 8x8 tile boundaries when you're in character mode. But there are a few alignment-related points of issue to mention.
The left edge of the first submenu always lines up with the left edge of its parent item in the menu bar. The left edge of deeper submenus line up with the right edge of the parent menu. The top edge of a sub-sub-menu lines up with the top edge of the parent item that opened it. All of that stuff is pretty obvious.
Within a menu, the item labels line up on the left, but are always inset one column from the left edge of the menu. That leaves a vertical trough, or track, one column wide, into which can be rendered a checkmark if the item is made active. More on how active and inactive menu items works below.
The keyboard shortcuts line up on the right edge. The submenu indicator arrow also lines up along the right edge. There is a minimum of one space between the last character of the label, and the first character of the keyboard shortcut description. There can be more than one space, if you have an item with a long label, the menu will be forced wide. If another item in the same menu has a short label, the end of the label is padded out before the keyboard shortcut, such that the shortcut lines up on the right with all the others.Keyboard Shortcuts
The possible keyboard shortcuts in C64 OS's menus are ultimately founded on the event model itself, which I wrote about in some detail last year, in The Event Model.4 Keyboard events are divided into two queues, with two different event structures. Printable key events, these are most akin to how the KERNAL handles key events. Each is just a single byte, and it uses the KERNAL's own keyboard buffer. And key command events. These are 2 bytes long, and have their own buffer.
Key command events have a byte to hold the key value itself, plus an additional byte to hold modifier key flags. The modifier keys consist of Commodore, Control, and Left and Right Shift independently. If only a shift key is used with a letter or number, that doesn't produce a key command event, but a printable key event. Either Commodore, or Control, or both together have to be held in concert with a letter or number to produce a key command event. If a key command event is being generated, the status of the shift keys is also captured in its flags byte.
Each screen layer is given the opportunity to process events, in order, from top to bottom. The menu system occupies the topmost layer, therefore it always gets first shot at processing events. Besides obviously processing mouse events, the menu system only pays attention to key command events. Printable events are passed through without being processed. Thus, it is impossible for a menu item to have a keyboard shortcut that consists only of Shift + some-letter. Such a combination always passes below the radar of the menu system.
Practically speaking, keyboard shortcuts must be unique. Technically, the menu data file can be used to assign the same keyboard shortcut to more than one menu item. However, when a key command event is being processed by the menu system, the menu hierarchy is walked recursively, in a deterministic order, until the first match is found. After finding the first match, even if that matching item is disabled, the search ends. So, duplicate keyboard shortcuts can be assigned, and they will even render! But when you type that combination, only the first found item will ever get triggered.
Speaking of rendering. Windows displays its menu shortcuts using abbreviated words and + symbols. As in the following example. (You might also notice that these menus leave a vertical left-hand track, reserving space for active checkmarks, just like C64 OS does.)
Examples include: Ctrl+Shift+H, Ctrl+Shift+G, or Ctrl+U. The Mac, whose interface consistency stretches back to the earliest days of System 1.0, and its 512 pixel wide screen, uses a symbol-based shortcut description that was clearly designed to conserve space. Each modifier key: Command, Control, Option and Shift has a unique single-character symbol, combined immediately with whatever the letter or number key is. So the longest possible keyboard shortcut description is only 5 characters. I also happen to think this system looks a lot cleaner than the wordy Windows way.
The C64 is starved of screen real estate, so it's frankly obvious which system is better suited to our favorite machine. Before switching to a custom character set, choosing which symbols to use to represent our 3 modifier keys was a bit contentious. What to use for the Commodore key? Originally I'd opted to use an asterisk for Commodore, a front slash for Shift, and the PETSCII up arrow for Control. Now that we have a custom character set, life is much easier.
I've opted for much more industry standard symbols. The outlined up arrow is for Shift. This is what the Mac uses, and this symbol appears directly on the shift key of many Mac and PC keyboards. For Control I'm using the caret symbol (^) this is a UNIX standard, and is also the symbol used in menus on the Mac. And lastly, for the Commodore key, one of the custom characters is a Commodore logo.Realtime Compositing
Macs and PCs today do realtime compositing. Applications do not merely draw on the screen and expect to own the screen at all times. Applications share the screen with other applications. C64 OS is not multi-tasking, but that doesn't mean it doesn't have to share the screen. There are Utilities (desk accessory style mini apps) which can be opened and rendered over top of the current App's UI. The system itself may throw up dialog boxes. And the menu system, which dynamically renders the menus to the screen, is a system-level, system-owned process.
I read in the GEOS programmer's reference guide a bit about how its menus composite. Before a menu, or a system dialog box, is opened, the OS copies the bitmap screen to a backup buffer. Then it draws the menu or dialog box overtop of the current screen. When the menu is closed, the region that was dirtied by the menu gets copied from the backup buffer back to the screen. The dirty regioning is for efficiency, so it doesn't need to waste time copying the entire buffer back to the screen.
There are some important things to be noticed here. In GEOS, when a menu, or a system dialog, is open, the application cannot update its display behind that menu. Furthermore, the programmer's reference guide explicitly says that the backup buffer is necessary, because it cannot be relied upon that the state of the application's screen can always be reconstructed. In fact, it says, in many cases it is impossible for the application to reconstruct its current screen. So, if the OS is going to cover it over with something, like a menu, it is the responsibility of the OS to be able to restore the area of the screen it overwrote.
Let me start by saying:
This flies in the face of the most fundamental design philosophy of C64 OS.
The reason is because of newer software design philosophies, such as Model-View-Controller. MVC, despite being invented early (like surprisingly many aspects of computer science) only became standard practice and alive in the mind of the typical software developer many years after the C64 was no longer a commercial platform.
The central idea is the separation, and disentanglement, of the data models, their presentation on a screen (or other output device), and the controlling logic that manipulates those data models and coordinates which models get rendered and when. In practical terms, that means that what is on the screen should never, not ever, be the sole instance of that data which the app has access to. So, you should never read a file from disk and spit it out into screen memory and be done. Because 2 seconds later the screen may get trashed by another process, and 2 seconds after that the system may require the app to redraw itself to the screen. (What are you going to do, go back to the disk again? What if the data was sent by the network, and can't be retrieved a second time?)
From some deprecated Apple documentation, Cocoa Core Competencies
The screen is 1000 bytes of character data, and a 1000 bytes of color data, that is maximally transitory. It's 2K of RAM that simply cannot be used for the persistent storage of data. No matter how tempting that may at times be. Instead, the app must load the data from disk into some other memory (which is the point of having a memory manager after all, you lean on that to find available space to use.) And when the system tells you to draw to screen, you copy (or render in some more sophisticated way) from the data elsewhere in memory into screen memory.5 You have no idea when the OS will ask you to rerender your data model(s). No idea, whatsoever. The system may ask you to rerender 5 times in one second. If your app can't do that, then you may notice stuttering, and you may have to take it upon yourself to make shortcuts via backups or buffers of your own in order to improve your rendering speed. Maybe.
But, if you stick to this design philosophy, which the OS strongly shoehorns you into, if you maintain the discipline of not violating the order of things, then the OS is able to do some very amazing stuff.
And you can see some glimpses of this in the video above. The background image is on a timer, redrawing itself in two different positions several times a second. The intermediate layer of draggable items in the App Launcher (the beginnings of what will become the App Launcher) render above that, an open Utility (not pictured in the video example above) can then render above that, and then open menus render still further above that. If your app sticks to the design philosophy, incoming data from a network, or a timer scrolling text, or baddies moving about in a game can continue to update themselves below and partially obscured by overlaying content. It's really, really cool.
It's also only possible in character mode, given the hardware constraints. So, that's one more reason we're in character mode.
A few more technical thingsKeyboard Shortcut Feedback
Back in April of this year, I wrote a post called Timers, One More Time. It outlined the timer system in C64 OS, and under the section, Why Are Timer Events Useful?, I promised I'd soon write a full post that goes into the nitty gritty details of the menu system. Well that promise is made manifest in the post you're reading now.
In discussing the usefulness of timers, I talked a bit about how they are used in menus to provide feedback when keyboard shortcuts are used. Here's what I said there:
On macOS when you trigger a menu item with a keyboard shortcut the root menu item (in the menu bar) blinks. This is a usability feature that provides feedback to the user to let him or her know that the keyboard shortcut actually triggered something that can be found somewhere under that menu, even though that menu is closed. The menus in Windows on PC, at least in my testing, do not do this. As a Mac guy, I find it mildly distressing and somewhat disorienting to use a keyboard shortcut, have something happen, but never see the feedback of the menu blink. The classic example is saving a document. On Windows you press CTRL+S and the document saves, but the menu doesn't blink. On the Mac, you hit Command-S and when you see that File menu blink you get this warm comforting feeling that you know it worked.
I implemented this usability feature from the Mac into the menu system in C64 OS, and I swear to you it took about 20 bytes of assembled code to do it. For just 20 bytes, that feels like a big win. But, it requires a timer to do it. The menu system already has recursive code that searches the menu hierarchy looking for a matching keyboard shortcut. As it moves from root menu to root menu it stashes a pointer to the current root menu being searched. If it gets to the end and never finds a match, that variable is nulled out. When it comes time to send the menu entry action code to the app, it makes one extra system call to mark itself as needing a clean (efficient, only affecting itself) redraw. Whenever the root menu's draw code is called, if the current root menu it's drawing matches the pointer in that variable, it changes the background color to the "open" color, but then clears that variable and sets a 0.2 second timer to make a system call for another clean redraw. When the timer expires, the menu bar is marked to draw itself again. But this time, the variable has been unset and so it redraws it with the standard background color. Net result? When you activate a menu with a keyboard shortcut, the root menu entry highlights, and then unhighlights automatically a 5th of a second later. It works perfectly, it looks great, the usability is (in my opinion as a Mac guy) brought way up. And it literally took around 20 bytes to implement. Plus two extra clean redraws are necessary, but they're negligible because they're so fast.
So that was that, I already discussed the implementation of the highlight feedback with keyboard shortcuts.Menu Bar Show/Hide
We are, let's face it, starved for screen real estate. There may be cases where you'd really rather not have the topmost 2560 precious pixels be permenantly occupied by a menu bar. There are many reasons why you might want to recover access to the top row. Maybe you just want more room for your content, or maybe your app is presenting something in fullscreen and you don't want the menu bar visually distracting from what's being presented.
I don't believe I've mentioned the following in this weblog before, so this is a good place to start. The service module maintains a status byte called redraw flags. (It's different than the flags that indicate if a particular screen layer is dirty and requires a redraw.) The system redraw flags are a set of bit flags, each indicates system level draw states. One of those flags is for whether the menu bar is showing or hidden. Another is for showing and hiding the status bar. The status bar is a file, disk and memory status bar that appears opposite the menu bar, at the bottom of the screen. In a future post I'll go into detail about that. For now, I'll just say that it can be shown or hidden. There are some other flags too. You saw in the video above that the menu bar clock can be turned on or off, that's a redraw flag.
The screen layers draw in order, from lowest to highest. Any code that draws in the top row of the screen will have that content overwritten by the menu bar when it draws itself last. If the the redraw flag for the menu bar is low, the menu bar will skip drawing itself. Keyboard shortcuts can still be activated, as was shown in the video, because it still intercepts and processes events.
An application can read the redraw flags to see if the menu bar is visible or not. It can use that to know how much vertical space it has, and when it's hidden the app can expand to fill the space.
Any application can programmatically turn the menu bar on or off with a system call. However, there is also a global keyboard shortcut (Control-Space, although, that's subject to change, or to being made configurable) to toggle the visibility of the menu bar at any time.Delegated Menu Item State
I haven't yet spent any time discussing how the menu system communicates with the application. Every C64 OS application starts with a standard table of pointers. One of those is to the menu command routine. Every menu item is associated with a unique, 1-byte, action code. When a menu item is triggered, the action code is put in the accumulator and the application's menu command vector is jumped through.
The application then has to decide for itself how the action code should be handled. I wrote a post earlier this year about a technique I came up with to make it easier to deal with these action codes. It's a Switch Macro. The thing I didn't really mention in that post is the the menu command routine is also used to delegate to the app the state of the menu items.
What do I mean by delegation? Fantastic question. I love the delegation pattern. I think it's really great.
Each menu item has some state associated with it. In C64 OS, a menu item can be either enabled or disabled, and it can be active or inactive. Disabled means it's visually greyed out, it doesn't have a rollover effect when the mouse rolls over it, if it is clicked nothing happens, and its keyboard shortcut doesn't do anything when pressed. Most menu items are enabled, most of the time. Active means the menu item has a checkmark beside it. Most menu items are inactive, most of the time.
That state is essentially two bits of data per menu item. One thing you could do is store those two bits in the menu item structure itself, along with the action code, keyboard shortcut, text label and the node pointers that link it into the hierarchy. When you want to change the state of a menu item you'd have to find the structure in memory for that item and set the bits accordingly. It might seem like that's the obvious thing to do, but it's actually kind of a pain in the ass to do so. There are other problems that arise, such as, what happens if you want the active/inactive state of a menu item, which cooresponds, say, with a feature that is active or inactive, to be saved to disk when the app quits?
Let's say the app is the file manager. And the file manager has the ability to toggle on and off the display of a sidebar panel for info on the selected file(s). You might have a view menu that has items under it that can be toggled on and off. Under "View" is "Details Panel". You use that to turn the panel on and off, and maybe you even give it a keyboard shortcut, that'd be handy. When the panel is visible, you want the menu item to have a checkmark beside it. When you quit the app, you want the app to remember the state it was last in. Nothing is more annoying than every time you open the app the panel is shut again (or open again) contrary to how you last left it.
It doesn't make any sense for the menu's own internal flags to be the truth about the state of your app. When you load the app, and load its configuration you'd have to have code to push that state into the menu items. When you save your configuration you'd have to read the flags back out of disparate menu item structures. And besides, your app's main logic needs to know the state of whether to draw the side panel or not. It makes most sense to have all of your app's state variables in one contiguous block of memory. That way it can easily be read from and written to disk as a single block. Your rendering code for the side panel, it knows exactly where to go to read its open status.
If the menus maintain their own status flags, then you just need a bunch of extra logic that tries to keep the application's own internal state synchronized with the state of the menu items. That sucks! Synchronizing of state stinks. It's not fun to code and it's usually buggy and error prone.In steps delegation.
The menu system (which is owned by the OS), and the menu item structures, in C64 OS do not maintain the active or enabled state information. They delegate that to the application. When a particular menu is being drawn, in realtime as it's drawing each menu item, the menu system makes calls out to the application asking it for the current state of that item. How does it actually do this? By using the same menu command routine that it uses when an item is triggered, but with the carry set.
Your app implements the menu command routine. If it's called with the carry set, the accumulator holds the action code of a menu item that is being inquired about. You load the state into the accumulator and return. The menu system draws that menu item accordingly. That's delegation. The OS-level menu system delegates the state logic of the items to the application. It is remarkably powerful. Let me give a couple of examples of why.
From some deprecated Apple documentation, Cocoa Core Competencies
Let's say your app wants to display a modal dialog. When the modal dialog is up, it's modal, that means you don't want the user interacting with other parts of the app until they've dealt with the dialog. You want to disable all of the menu items. Without delegation, you'd have to recursively walk the menu hierarchy (which by the way, the OS-level menu system does not directly expose to the application. So the app technically doesn't know where that memory has been allocated.) And you'd have to manually toggle the flags on every item. But, what would happen if you encounter an item that has already been disabled? You've now got two reasons for why that item should be disabled. When the modal dialog goes away and you've got to re-enable every item, you've got to recursively walk the hierarchy again. But you can't just blindly re-enable everything, you need to store and restore which items were meant to still be disabled for another reason.
It's sucks. It's a hairy mess. I'll bet you dollars to donuts GEOS does not handle this well.
Now think about how it's handled with delegation. You maintain a single variable in your app for when you're showing something modally. Whenever the menu command routine is called with the carry set, it's inquiring about a menu item. First thing you do is check your modal flag. If you're displaying something modal, then who cares what menu item is being inquired about? All you do is return a generic state byte of "disabled". The menu item draws as disabled, regardless of whatever other state for it may be stored somewhere else.
When the modal dialog goes away, and you set your modal flag low, the next time a menu item inquiry is made it gets passed that part of the code and into the rest. At this point, all you do is load your application's primary state data for that feature into the accumulator and return. Done.
Not only is it easier to maintain, less error prone, and requires less code, but it's also on-demand. It's first come first serve. If the user happens to never try to activate the menus while the dialog is open, then the ONLY state change was that one solitary modal flag. Without delegation, you have to anticipate that maybe the user will touch the menus, and you have to change the state of all of them, just in case. And after that, you have to carefully set them all back. That's Not Fun.
Delegation is The Bomb.
An inquiry is made about each menu item, every time such information is need. Everytime the menu has to be redrawn, it makes one call for each item, as that item is being redrawn. When a key command event arrives, the menu system searches the menus for a match. If it finds a matching item, it makes one inquiry call for that item. If it's disabled, it will not call the menu command routine again, aka, it will not trigger that item.
Another example of why delegation is great, is with the active state. You might have a button, or a checkbox in the UI, (Toolkit widgets), that backends on the same state data. The Toolkit makes use of the delegation pattern too. When you click a checkbox, some delegated state is changed. When the UI redraws, the checkbox asks for that state from the app, and draws itself checked. But a menu item might represent the same feature, and backend on precisely the same state data. So when the menu renders, the menu item automatically shows as active. And if you click the menu item to deactivate, the checkbox automatically redraws as unchecked. In other words, different pieces of UI can backend on the same state, rather than having to keep them synchronized manually.
Okay. I got this published before the end of June. It's another whopper, over 10,500 words. I hope this gives you a detailed look into what I've been doing with the C64 OS menus. And I hope you'll agree that they are quite modernly designed.
- 64. [↩]
- Although I have put in a feature to help with that that wasn't available in Mac OS X until v10.11, in 2015. (Hiding the menu bar. But, we'll get to that.) [↩]
- See my Programming Reference, VIC-20 / Commodore 64 SuperChart, for a description of PETSCII and ScreenCode blocks. [↩]
- Most of that post has remained unchanged over the last year and half, except for the keyboard mapping of special ASCII characters. I'll be writing another article soon about the custom character set, and my latest thoughts on key mapping. Teaser: I'm leaning more towards nativism, and less on aping a PC/Mac keyboard's layout. [↩]
- It actually gets more complicated than that, because C64 OS double buffers the screen. But we can ignore that for now, and simply pretend that the double buffer is the screen. [↩]
Do you like what you see?
You've just read one of my high-quality, long-form, weblog posts, for free! First, thank you for your interest, it makes producing this content feel worthwhile. I love to hear your input and feedback in the forums below. And I do my best to answer every question.
I'm creating C64 OS and documenting my progress along the way, to give something to you and contribute to the Commodore community. Please consider purchasing one of the items I am currently offering or making a small donation, to help me continue to bring you updates, in-depth technical discussions and programming reference. Your generous support is greatly appreciated.
Greg Naçu — C64OS.com