Logo FS-Skia-UI

Input

FS.Skia.UI's input story is built from pure, host-independent reducers: the raw event stream from the SkiaViewer host is translated into typed messages, fed to an Elmish-style update, and turned into a list of effects that the application interprets. Nothing in the input packages opens a window, polls a device, or draws to the screen on its own. This page covers the two keyboard packages that share the input slug — the richer FS.Skia.UI.Input runtime and the lightweight FS.Skia.UI.KeyboardInput package — and explains where pointer/mouse input actually lives. See the API reference for the full surface.

Naming caveat (read this first). Despite the landing-page table describing FS.Skia.UI.Input as "Pointer/mouse input events", the package's public surface today (namespace FS.Skia.UI.Input) is a keyboard binding, command, mode, sequence, and bigram-analysis runtime — there are no pointer or mouse types in it. The actual pointer/mouse contract is the host's ViewerEvent cases plus a pointer front door that lives in FS.Skia.UI.Controls, described at the end of this page. This documentation reflects the code, not the label.

Two keyboard packages, two scopes

Package

Namespace

Scope

FS.Skia.UI.KeyboardInput

FS.Skia.UI.KeyboardInput

A small key→command binding reducer with mode-stack/layout state and a state-display snapshot. Depends only on Scene.

FS.Skia.UI.Input

FS.Skia.UI.Input

A full keyboard configuration runtime: YAML-driven bindings, command registry, modes/states, multi-chord sequences, command intents, diagnostics, bigram ergonomics analysis, and on-host state-display rendering. Depends on Scene and SkiaViewer.

The two are independent — Input is not built on top of KeyboardInput; they are separate packages with overlapping concepts (both have KeyDown/KeyUp messages, a pressed-key set, a mode stack, and a state-display projection).

FS.Skia.UI.KeyboardInput — the lightweight reducer

This package is deliberately minimal. You give Keyboard.init a list of KeyboardBinding records ({ Key; Command }) and it returns a KeyboardModel plus startup effects. Keyboard.update is the reducer over KeyboardMsg (KeyDown, KeyUp, FocusLost, Reset, SetActiveLayout, PushTemporaryMode/PopTemporaryMode, SetPersistentMode, ResolvePendingSequence). When a KeyDown matches a binding it emits a CommandResolved KeyboardEffect; other effects report key-state, layout, mode, pending-sequence, and diagnostic changes.

A second module, ViewerKeyboard, normalizes raw host key strings into a typed ViewerKey (ArrowLeft, Enter, Letter of char, Digit of int, Function of int, Unknown of raw, …) and converts it to the KeyId the bindings use. That is the adapter between the host's stringly-typed key names and the package's vocabulary.

Keyboard.stateDisplay projects the model into a KeyboardStateDisplay snapshot (pressed keys, active layout, mode stack, pending sequence, last command) for HUD/state-overlay rendering.

FS.Skia.UI.Input — the configured keyboard runtime

The Input package is a much larger system aimed at apps that want a data-driven key map with modes and chords (think of a modal editor's keybinding file). The pipeline has four stages, each returning a Result<_, InputDiagnostic list> so configuration errors are explicit values, not exceptions:

result {
    let! registry      = KeyboardInput.commandRegistry commands       // CommandRegistry
    let! configuration = KeyboardInput.parseYaml yaml                  // InputConfiguration (YamlDotNet)
    let! model         = KeyboardInput.validate registry configuration // CanonicalInputModel
    let! runtime, _fx  = KeyboardInput.init configuration.DefaultLayout model
    return runtime
}
  1. Registry. KeyboardInput.commandRegistry takes the CommandDefinition list the bindings may emit and rejects duplicate command ids.
  2. Parse. parseYaml reads a YAML document (via YamlDotNet) into an InputConfiguration — layouts (LayoutProfile with KeyPositions carrying Hand/Finger/row/ column), modes (ModeDefinition of kind StandardMode/StatefulMode/ PopupMode/TemporaryHeldMode), bindings, disambiguation timeout, an optional bigram profile, display options, and command intents.
  3. Validate. validate cross-checks the configuration against the registry — unknown modes/commands/layouts, duplicate bindings, invalid mode states — and produces a CanonicalInputModel.
  4. Init. init builds the InputRuntime: the canonical model plus a mode stack, a pressed-key set, an optional pending sequence, the active layout, an event log, and accumulated diagnostics.

The runtime reducer

KeyboardInput.update folds an InputMsg (KeyDown, KeyUp, FocusLost, Timeout, SetLayout, Cancel) into a new InputRuntime and a list of InputEffect (CommandResolved, LayoutStateChanged, InputDiagnosticEmitted, InputEventRecorded). The control flow is honest about ambiguity and error recovery:

KeyboardInput.viewerInputMsg maps a host ViewerEvent (KeyDown/KeyUp/CloseRequested) to an InputMsg, and updateFromViewerEvent steps the runtime straight from a host event. KeyboardInput.replay folds a whole InputMsg list, which makes recorded-input replay deterministic.

Projection, rendering, and analysis

KeyboardInput.layoutState projects the runtime into a LayoutStateView, and a family of keyboardStateDisplay / renderKeyboardStateDisplay(At) / renderLayoutState(At) functions build a KeyboardStateDisplayModel and render it to a Scene for an on-host overlay, with default, compact, and expanded option presets. KeyboardInput.analyzeBigrams produces a BigramReport — top command pairs, ergonomic BigramRisks (same-finger, long-travel, awkward-hold, same-hand-repeat), and reassignment BigramSuggestions — using the layout's hand/finger metadata.

Where pointer / mouse input lives

Pointer interaction is not in either input package. It is split across two other subsystems:

So the data flow for a click is: host raw event → ViewerEvent.PointerPressed → consumer maps to PointerSample → the Controls pointer reducer hit-tests via Layout and emits a click interaction → the Controls.Elmish bridge lowers it to a Cmd<'msg> for the Elmish runtime. The keyboard packages follow the same shape with ViewerEvent.KeyDown/KeyUp.

How input fits the framework

Both keyboard packages are pure reducers that sit at the same architectural position: the host emits raw events, an adapter normalizes them into typed messages, update produces a new immutable runtime plus effects, and the Elmish/MVU runtime interprets those effects as commands. The packages render into scenes (for state overlays) but never own the frame loop, and they reach controls only through the neutral hit-test geometry that Layout computes.

Analysis

Implementation strengths

Implementation weaknesses

Design pros

Design cons

Type something to start searching.