LisaGUI: move/copy code refactoring, and modal blocker information
I ought to document a bit more about what's happening with LisaGUI behind the scenes, even if the post isn't flashy.
LisaGUI had a lot of disk code pertaining to moving and copying files which was located in the Desktop Manager's (i.e. the file manager) controller - lets call it the FolderController. I recently refactored it out of there and into the disk controller class, turning a few hundred lines of code in FolderController to a few calls to DiskController methods.
This made things much neater in the "endDrag" function, a FolderController function that runs when you release the mouse button after dragging file and folder icons (as you might have guessed). And so, I was able to immediately knock off another item on my todo list - adding file copy dialogs. They appear when you copy files from one disk to another. They weren't really that necessary, since "file copying" within an IndexedDB database in a modern browser is much quicker than the read and write speeds of floppy and hard disk devices back in 1983... I actually have to add an artificial delay to the wait dialog so it's visible long enough for you to see it.
endDrag is an asynchronous function called by another function, onDragEnd, which is a generic function that runs whenever a drag finishes on a draggable ScreenElement (the base class from which every object displayed within LisaGUI's screen is derived). The call to endDrag prevents any other mouse input from occurring. All mouse inputs, like pointermove, pointerup, and pointerdown, are handled by functions in the system class which are sent to a single queue so they execute consecutively.
I'd like to note that I'm not actually overriding the pointer handler functions on each ScreenElement. I concocted my own little component - I call it a function array. I overrode the base Array class with a bit of syntactic sugar. You can declare a new FunctionArray, push some functions into it, and then call .run() on it with as many arguments as you want. run() simply loops through the array and consecutively calls every function in it with the given arguments. All the accumulated return values are pushed to an array which run() returns. I'm using it for all the input handlers on the base ScreenElement class so I can push functionality to them in derived classes without overriding the entire function. It means I can selectively keep or remove the existing functionality of the handler in derived classes. This makes more of a difference if you are two or three (or four!) derived classes away from the base class... calling super() can be clumsy sometimes, but this gives me direct access to base class member functions. I can add to the array, replace its contents entirely, or do anything in-between.
Adding an artificial delay significantly holds up the completion of endDrag, which also holds up the completion of onDragEnd, which ultimately has the user-facing effect of freezing mouse input while the dialog is open. That's just not an option. This wasn't a problem before because the time it takes to copy most files has been negligible, at least in my own testing... ("It works on my machine!™ ")
I was able to fix this issue by making the call to endDrag synchronous. However, there's still the question of preventing unwanted input while the file copying was occurring, since this frees up the mouse to move and click on other items. FolderController keeps track of the state of a drag, but I can't predict what other inputs a user might perform during a file copy that would cause problems down the line. This is partially solved by the modal dialog, but these dialogs aren't always used in this scenario.
All objects in LisaGUI are part of a large tree data structure; the desktop itself is the root of this tree. The desktop has several children with equal dimensions to itself placed at x/y coordinates (0, 0). They effectively function as "layers" on top of it. One of them holds the file and folder icons on the desktop. Another holds all the windows, another holds the menu bar. LisaGUI's modal dialog system places dialogs on a special layer, along with a large, invisible blocker object that takes up the entire screen behind it.
The blocker has a dual purpose - it can be used to prevent clicking on anything else but the dialog, forcing the user to interact with it before proceeding, or as a way of exiting the dialog by clicking outside of its bounds. Like pointer inputs, dialogs are put into a queue; they aren't retrieved until all other dialogs in the queue have been closed, or "resolved." Certain options, like the functionality of the blocker element, are given to the system as additional parameters when the dialog is queued up.
Another such parameter is an override for the mouse pointer. Wait dialogs have already been doing this recently; the pointer is overridden from the normal arrow cursor to an hourglass. I soon realized that this would also be a great way of blocking input in general, without actually displaying a dialog. All one needs to do is push a small, 1px by 1px ScreenElement as a modal with the right parameters. The call to resolve it can be placed in the finally block of a try/finally call, and the important, uninterruptible task goes in the try block. Try finally isn't absolutely necessary, but it's something I'd like to do when blocking off the screen with no other way to unblock it.
I've done this in a couple places and I'm now doing it with endDrag; it creates and queues an invisible modal at the start of the functon. When the code determines a dialog is going to be needed, it resolves the invisible modal and queues a visible "wait" dialog. Either way, both are resolved at the very end of the function, once all the cleanup completes and the artificial delay is finished.
Thus, there will now always be a split second where you see an hourglass cursor after dragging an item. I cannot overstate how much of a difference a busy cursor makes in helping LisaGUI feel more like a real operating system. You encounter them all the time on desktop OSs - especially on older systems - so seeing it makes me feel right at home. It's also a nice side effect of preventing a user from breaking things while the filesystem is being modified.
I plan to add blockers and dialogs in a couple other places, including when you save a file. One place I've already done so is when a user tries to drag an item into a "locked disk." This specifically refers to the read-only "diskettes" used to add spare stationery pads to LisaGUI (in case you decided to delete them). These are just "RAM disks." They're created when the system starts, and discarded on shutdown. If I made them editable, people might drag files onto them, and complain when the files aren't there after they've quit and reopened LisaGUI... (Note: LisaGUI's treatment of read-only volumes is more Mac-like than the actual Lisa).
One thing I need to consider is the question of how to handle the duration of artificial delays. I think the best thing to do would be to tie them to the startup delay feature, and have it control the speed of all artificial delays, and maybe even all the system animations. Maybe I can call it an "overclocking" feature! Although technically speaking, it's more like underclocking... I could also split this across multiple settings - one for startup, one for system animations, and one for dialog delays.