Skip to content

Latest commit

 

History

History
168 lines (122 loc) · 6.98 KB

access-keys.md

File metadata and controls

168 lines (122 loc) · 6.98 KB

Access keys

  • Pull request: #8

Scope: keyboard shortcuts as shown by underline

Status: implemented (simple); possible improvements

Introduction

Several traditional UI systems support mnemonics, also known as "access keys". Primarily these are used on menus:

  • Hold Alt to underline "mnemonic" keys in labels
  • Press e.g. Alt + F to open the File menu

Such labels are often configured using simple markup: e.g. &New may be the title of a menu item with a mnemonic bound to Alt + N.

One common limitation of such systems is that, should mnemonic keys happen to clash, only one such item is reachable through the mnemonic key though others may still be underlined.

Requirements

Mnemonics should:

  • Be configurable via simple markup: &File, &Reload, Reload &All etc.
  • Map upper/lower-case: e.g. &Load and Re&load both bind to key L.
  • Be driven by localisation (translations), not hard-coded names/keys.
  • Display an underline on configured letters in mnemonics. Ideally, only underline usable mnemonics, not clashing keys or those from a different "layer" (e.g. a parent menu).
  • Optionally, we could support multiple mnemonics per label with the toolkit automatically selecting one to use, or even automatically selecting a letter in labels without a configured mnemonic.

Note: theoretically, labels could support further markup, allowing e.g. bold items or superscript (though Unicode already has significant support for this). In this case we would want a (custom) DSL not a general markup language like Markdown.

Questions

Concerning internationalisation:

  • Are mnemonics applicable to CJK languages whose alphabets are much larger than the number of keys on a keyboard?
  • Are there issues with Arabic where letter form may alter substantially?
  • There likely will be issues where a ligature is used, e.g. Æ, ij.
  • Are there issues with accented keys entered via deadkey? Should, say, &Écran be activated by E? French keyboards do typically have an É key, but on a Swiss-German layout this letter is only available via a third layer (AltGr) and on a Romanian layout only via a dead-key sequence.

Alternatives

Some apps such as basic calculators will want to bind keyboard keys without the need to press Alt to activate mnemonics. This could use a different approach entirely, but Kas currently uses mnemonics with a special "alt bypass" mode.

The toolkit does not have to support mnemonics: not all UIs use them. They could even be considered outdated. There might be conflicts with external accessibility tools (unknown).

An alternative approach to keyboard-assisted menu navigation is search: e.g. a keyboard shortcut to open a list of commands which may be filtered by typing, or the approach used by Android's settings (a choice of navigable menus or using a search box).

Existing design

The current design used in Kas is as follows:

  • Mnemonics are assigned to "layers". One base layer is used for the main app while each popup has its own layer, whose keys are only active when that popup is open.
  • The AccessString type parses mnemonic keys during construction and assignment.
  • Widgets register valid mnemonics at configuration time.
  • Pressing Alt triggers a redraw; the AccessLabel widget draws underlines only when Alt is pressed.
  • fn start_key_event handles matching mnemonics from layers.
  • A widget with an active mnemonic is considered "depressed", allowing e.g. calculator buttons to move visually with the corresponding keyboard keys.

This is a piecemeal design which is functional with limitations:

  • Widgets must register themselves during configuration. This registration system is mostly static and may not fully track the state of the UI.
  • Mnemonics are not tied to visibility with the exception of popups. This can result in conflicts and in hidden widgets (e.g. from another page of a TabStack widget) having active access keys.
  • Mnemonics are underlined in labels even when a key conflict makes them unusable and when their configuration layer is not active.

Improved designs

Objectives are to fix the limitations above.

Mnemonic pages

Currently only the Window and Popup widgets have access-key layers.

Additional layers could be used such that, for example, a Stack widget could allocate a new layer for each page, thus avoiding having active access keys on hidden pages (partially fixing the visibility limitation).

This would require some new mechanism to track active Stack pages.

Event broadcast

A larger modification is to remove configuration "layers" and registration of mnemonics entirely. Instead, an event notifying of the mnemonic key is broadcast to all visible widgets on press; the broadcast may be terminated once any widget consumes the event.

This requires two new capabilities: event broadcast, and obtaining a list/range of visible children (we could probably use Events::recurse_range for the latter).

This fixes two limitations: the need to register mnemonics and tying mnemonics to visibility.

Caveat: parent menu mnemonic keys will be active and take priority over submenus, unless additionally broadcast is limited to the last popup (possibly closing that and repeating on no match).

This is likely the best option, but requires some adjustment to core widget methods.

Mnemonic activation registration

An alternative to removing registration entirely is to perform this temporarily when Alt is pressed and when state changes until Alt is released.

Registration could happen from Events::update. Caveat: update is supposed to be very fast, though likely this is a non-issue. Caveat: widgets sometimes update only part of the tree, e.g. when adding a child to a List or when switching the page of a Stack. We would need to either add a mechanism to unregister sub-tree registrations or to disallow partial-tree updates.

This design would allow fixing the final limitation regarding underline of conflicting mnemonics by providing feedback during this registration action, but only if registrations are added on a first-come-first-served basis (which, as with the above design, would require restriction to the top-most popup).

Shadow tree

We could copy the model used by AccessKit: build a shadow model of the widget tree (limited to nodes which are visible and have either children or access keys). This shadow tree would need to update any node whose list of visible children changes.

The caveat of this approach is that it does a lot of work just to build a limited copy of the widget tree we already have. The advantage is that it would work very similarly to AccessKit, and might even be swapped out at run-time (with the implication that access keys are not available when using an external accessibility tool).