From e6d44dacaf24fbdd793c8d758178a5a97245e1d9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 30 Aug 2023 14:04:39 +0100 Subject: [PATCH 1/2] Add Access Keys --- input/access-keys.md | 167 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 input/access-keys.md diff --git a/input/access-keys.md b/input/access-keys.md new file mode 100644 index 0000000..32fca8f --- /dev/null +++ b/input/access-keys.md @@ -0,0 +1,167 @@ +Access keys +=========== + +- Pull request: [#1](https://github.com/kas-gui/design/pull/1) + +**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 `L`. +- Be driven by localisation (translations), not hard-coded names/keys. +- Display an underline on configured letters in mnemonics. +- Ideally, only usable mnemonics should be underlined: e.g. if two menu items + bind `B` only the usable one should be underlined. +- 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. + +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". Typically one base layer is used for the + main app and a new one for each menu. One layer is active, but mnemonics + from lower layers may be used (with the side-effect of closing any popup + associated with the higher layers). +- 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: + +- The visually-depressed state is currently broken for button-label mnemonics + since the *label* is considered depressed, not the button. +- Widgets must register themselves during configuration. If used through a + view-list it might result in a very large number of registrations. Further, + all children of stacks, lists etc. are considered part of the same "layer", + potentially resulting in many conflicts and hidden widgets being activated. +- Conflicting mnemonics still get a visible underline. +- Mnemonics are not tied to visibility excepting where a layer is used + explicitly (mostly for menus). This can have surprising results, especially + when mnemonics conflict. + + +Improved designs +---------------- + +Objectives are to fix the limitations above. + +### Pass physical key identifier + +This is a very minor modification of the existing design. `AccessLabel` pushes a +`kas::message::Activate` when a mnemonic is used; the key identifier (e.g. +scancode) may be attached as a payload thus allowing the button consuming this +message to register itself as depressed until this key is released. + +This fixes only the visually-depressed state issue. + +### 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 (already implemented). + +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). + +### 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`, though this is insufficient if +e.g. `Stack` uses sub-tree update when changing page (full-tree update would be +needed to remove mnemonics from the old page). + +This allows 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 (may not be ideal if +parent menus may have active mnemonics when a sub-menu is opened). + +Although it uses registration, it does not suffer from the issues of the +existing design since the registration list is limited to "visible" widgets and +is not long lived. The catch is that the definition of "visible" used by +`Events::update` does not correspond exactly with what is visible on the screen +(though it may be a good thing if scrolling does not hide mnemonics). + +### Mnemonic pages + +Similar to the prior design, register all mnemonics to pages, but unlike the +prior design the "popup" should be the root of the page (not its parent). +This does not fix any of the above issues. + +Use of additional sub-pages (e.g. for `Stack` pages) partially addresses the +visibility issue, but requires some new mechanism to track active `Stack` pages. From a720f5ab37d3e51f3c5be0d335841f610cedb797 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 30 Aug 2023 14:36:57 +0100 Subject: [PATCH 2/2] Update access-keys --- input/access-keys.md | 103 ++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/input/access-keys.md b/input/access-keys.md index 32fca8f..c3a2b3f 100644 --- a/input/access-keys.md +++ b/input/access-keys.md @@ -1,7 +1,7 @@ Access keys =========== -- Pull request: [#1](https://github.com/kas-gui/design/pull/1) +- Pull request: [#8](https://github.com/kas-gui/design/pull/8) **Scope:** keyboard shortcuts as shown by underline @@ -29,11 +29,11 @@ may still be underlined. 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 `L`. +- 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 usable mnemonics should be underlined: e.g. if two menu items - bind `B` only the usable one should be underlined. +- 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. @@ -65,7 +65,8 @@ 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. +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, @@ -78,10 +79,9 @@ Existing design The current design used in Kas is as follows: -- Mnemonics are assigned to "layers". Typically one base layer is used for the - main app and a new one for each menu. One layer is active, but mnemonics - from lower layers may be used (with the side-effect of closing any popup - associated with the higher layers). +- 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. @@ -93,16 +93,13 @@ The current design used in Kas is as follows: This is a piecemeal design which is functional with limitations: -- The visually-depressed state is currently broken for button-label mnemonics - since the *label* is considered depressed, not the button. -- Widgets must register themselves during configuration. If used through a - view-list it might result in a very large number of registrations. Further, - all children of stacks, lists etc. are considered part of the same "layer", - potentially resulting in many conflicts and hidden widgets being activated. -- Conflicting mnemonics still get a visible underline. -- Mnemonics are not tied to visibility excepting where a layer is used - explicitly (mostly for menus). This can have surprising results, especially - when mnemonics conflict. +- 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 @@ -110,14 +107,15 @@ Improved designs Objectives are to fix the limitations above. -### Pass physical key identifier +### Mnemonic pages + +Currently only the `Window` and `Popup` widgets have access-key layers. -This is a very minor modification of the existing design. `AccessLabel` pushes a -`kas::message::Activate` when a mnemonic is used; the key identifier (e.g. -scancode) may be attached as a payload thus allowing the button consuming this -message to register itself as depressed until this key is released. +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 fixes only the visually-depressed state issue. +This would require some new mechanism to track active `Stack` pages. ### Event broadcast @@ -127,41 +125,44 @@ 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 (already implemented). +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 +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`, though this is insufficient if -e.g. `Stack` uses sub-tree update when changing page (full-tree update would be -needed to remove mnemonics from the old page). - -This allows 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 (may not be ideal if -parent menus may have active mnemonics when a sub-menu is opened). - -Although it uses registration, it does not suffer from the issues of the -existing design since the registration list is limited to "visible" widgets and -is not long lived. The catch is that the definition of "visible" used by -`Events::update` does not correspond exactly with what is visible on the screen -(though it may be a good thing if scrolling does not hide mnemonics). - -### Mnemonic pages - -Similar to the prior design, register all mnemonics to pages, but unlike the -prior design the "popup" should be the root of the page (not its parent). -This does not fix any of the above issues. - -Use of additional sub-pages (e.g. for `Stack` pages) partially addresses the -visibility issue, but requires some new mechanism to track active `Stack` pages. +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).