Jul 2022

Another month without much to show (well, there is a little bit to show related to mouse cursors). I fell short of the main goal: porting EditField to prodUI. This sucks, but it’s worth taking the time to get it right, as so much in a game or application depends on the UI layer.

No video once again. Shucks.

 

Devlog

Summary

  • Worked on editField, prodUI and demoUI. Again, nothing really to show right now.
  • Worked on keyboard manager with virtual key-repeat events. (Not yet released)
  • Released libraries and snippets:
    • cursorMgr: a LÖVE mouse cursor manager / singleton (work in progress)
    • mouseCursorPack: a set of CC0 mouse cursors (also a WIP)
    • modKeyState: a LÖVE snippet which demonstrates a temporal / ordering issue with using isDown() within love.keypressed(), and a workaround

 

cursorMgr

cursorMgr is a mouse cursor manager / singleton. It can use the LÖVE hardware cursor API, or render the cursor as a quad at the end of love.draw() (though you need to provide your own cursor art for that). It supports animated cursors, and provides a priority slot system where only the highest non-false entry is displayed.

demoUI and prodUI use a priority system similar to this:

  1. Application-global busy/wait
  2. Context high priority
  3. Widget high priority (clicking, dragging)
  4. Widget low priority (idle hover)
  5. Context low priority (idle, not hovering over any widgets)

This allows widgets to specify an idle or drag cursor without stomping over the context idle cursor. Likewise, application busy/wait can be set independently of other slots.

cursorMgr is not a drop-in solution: you need to write some logic to load the cursors in the expected table format and assign them. If you are going to use custom cursors, I think it’s a good idea to always provide the quad-based rendering codepath as an optional fallback.

 

mouseCursorPack

Some of the cursors included in the pack.

I couldn’t find any collections of mouse cursors with permissive / minimal-strings-attached license terms, so I made one. All cursors are black-and-white and available at the following resolutions:

  • Pixel art: 8×8, 12×12, 16×16, 24×24, 32×32
  • Vector art: 48×48, 64×64, 96×96, 128×128, 192×192, and 256×256

The pixel art uses cut-out transparency with rough outlines, while the vector art is interpolated and has soft edges. Hotspot locations are stored in one of two ways: either embedded into the file names, or included in a plain-text file with the same file name but a ‘.hotspot’ extension.

The example and export code is MIT, and the art is CC0. The script to export cursors is a horrible mess right now, and I’ll need to revisit it in the future to make it more robust and to ensure it works on Windows.

 

EditField Status

EditField itself is mostly done, but various issues in the demoUI widget implementation remain, and this is holding up progress on the prodUI implementation (which is, in turn, holding up almost everything else).

The demoUI widget had scrollbar-like indicators which showed the current viewport location in the document. Since I apparently hate myself, I upgraded them to fully drag-able scroll bars. Here they are, in oversized form:

 

As a result of this, I now have some generic functions for positioning and resizing scroll bars such that the “thumb” can always move to the far edge of the container without a gap. (This comes at the modest price of requiring the container, scroll position, scroll bar trench and thumb all having non-fractional integer sizes and positions. But if you can guarantee that, it seems pretty solid.) I should throw together a snippet for this sometime.

I need to add some kind of tweening support for scrolling, and this will require a per-frame update callback. The demoUI widget hasn’t been designed to support this, so I have to restructure it to ensure that the cached update functions fire at the right time and in the correct order.

 

modKeyState

This small snippet demonstrates an ordering issue which can occur if you mix love.keyboard.isDown() inside of the love.keypressed() callback. (I’ve done this a lot! It’s a very quick and tempting way to read the state of modifier keys.) The solution provided is to track the state of modkeys in a separate table as changes are detected in love.keypressed() and love.keyreleased().

 

keyMgr (not yet released)

While messing with editField, I realized that the repeat events provided by love.keypressed(key, scancode, isrepeat)are slightly different on Windows and Linux:

  • Windows allows modifier keys (ctrl, alt, etc.) to generate repeat keypresses. Linux doesn’t. (*1)
  • When holding a non-modifier key and pressing a modifier (for example, holding Z and tapping shift): Windows ceases to generate repeat events for the non-mod key, while Linux continues to generate them.
  • During low-framerate conditions, Windows rate-limits the number of repeat key events to 1 per frame, while Linux appears to let an arbitrary number of repeat events through.

(*1) Okay, on Linux, sometimes repeats do sneak through on modkeys on the very first application frame, or if I mess with the window with the mouse. It’s infrequent enough that I believe it’s a glitch, or possibly a mitigation for a previous input issue.

I haven’t tested MacOS due to not having a Mac, but I wouldn’t be surprised if there is slightly different behavior there as well.

I began work on a keyboard manager that implements virtual key-repeat events that should behave the same across different platforms. It basically works, though there are a couple of problems. First: the virtual repeat events happen out of order from LÖVE events. How much of a problem is it? I’m not sure, and likely won’t know until keyMgr sees some use in the wild. Second, I have no solution for unifying the key-repeat events of love.textinput() with keyMgr. This makes the virtual repeat stuff a little less useful — no one wants the arrow keys, backspace, etc., to have a different delay+interval than regular text input. However, I still need this kind of behavior for joysticks and gamepads, so I might as well leave it in so that I can copy it over later.

Ignoring virtual repeats (which can be disabled) for a moment: keyMgr provides merged flags for modifier keys with multiple key appearances (ctrl == lctrl or rctrl), and it also maintains a hash of scancodes which are known to have been pressed on the current frame. It works primarily with scancodes (in LÖVE’s case, this means strings which correspond to the US QWERTY layout), but all input-reading functions come with keyConstant versions. Finally, all of this is in-order when queried inside of LÖVE event callbacks.

I’m holding off on a proper release of keyMgr until I get a chance to consider how it will interact (or not) with joysticks and gamepads, and how it will work (or not) with fixed timestep game logic running within a variable timestep application.

 

ProdUI: Removing event buffering

I decided to scrap the buffered event queue in prodUI. LÖVE/SDL events are already buffered, but this was a sort of secondary virtual buffer, where acting on events could be delayed to future frames. Here are my reasons for removing it:

First, some LÖVE/SDL2 events are not a good fit for inter-frame buffering. In particular, the application should respond as soon as possible to window change events like love.resize(). But doing so can invalidate pending buffered events. If a resize were to happen while several mouse or keyboard events are pending, and the resize affects the layout of widgets, then we either have to scrub the pending events, or hope that they don’t lead to an unintended action. Better to work with events in the order that LÖVE’s event system provides them, in the frame in which they are relevant.

Second, my buffered system didn’t accommodate per-frame updates. This makes it difficult to implement any widget action that isn’t directly the result of user input or any other LÖVE/SDL event. I considered adding a buffered event to represent love.update(), and maybe that would solve the issue in some cases, but it’s likely not worth the added complexity.

My imagined use case for buffered events was to allow the user to type or click additional commands while waiting for UI animations to play out. In practice, it’s not worth designing the entire UI layer around this, and similar kinds of input buffering can be achieved by other means.

 

Closing

That’s all I’ve got! (Argh.) Next devlog post should be around the end of August.