Selkie.git | docs/api/ | Selkie--Event.md
NAME
====
Selkie::Event - Keyboard, mouse, and resize event abstraction
SYNOPSIS
========
```raku
use Selkie::Event;
use Notcurses::Native::Types;
# In a widget's handle-event method:
method handle-event(Selkie::Event $ev --> Bool) {
return False unless $ev.event-type ~~ KeyEvent;
given $ev.id {
when NCKEY_UP { self!cursor-up; return True }
when NCKEY_DOWN { self!cursor-down; return True }
when NCKEY_ENTER { self!activate; return True }
}
# Printable character?
if $ev.char.defined && $ev.char.chars == 1 && $ev.char.ord >= 32 {
self!insert-char($ev.char);
return True;
}
False; # not consumed — bubble to parent
}
```
DESCRIPTION
===========
Every input event — keystrokes, mouse clicks, terminal resizes — is wrapped in a `Selkie::Event` before reaching widgets. The event carries:
* An `id` — the keycode (`NCKEY_*`) or character codepoint
* A `char` — the effective printable character, if any (handles Shift correctly: Shift+1 → `'!'`)
* The `modifiers` that were held — a `Set` of `Modifier` values
* The `input-type` — PRESS, RELEASE, REPEAT (see `NcInputType`)
* The `event-type` — `KeyEvent`, `MouseEvent`, or `ResizeEvent`
* Mouse coordinates (`x`, `y`) for mouse events
Widgets implement `handle-event(Selkie::Event)` returning Bool. True means the event was consumed; False lets it bubble to the parent chain and eventually to the app's global keybinds.
This module also exports [Keybind](Keybind) — the parsed form used by `on-key` on widgets and `Selkie::App`.
EXAMPLES
========
Character input
---------------
When the user types a printable character on a focused widget, you get it in `$ev.char`:
```raku
if $ev.char.defined && $ev.char.chars == 1 && $ev.char.ord >= 32 {
# Printable — insert into buffer
$!buffer ~= $ev.char;
$!change-supplier.emit($!buffer);
return True;
}
```
Note the `.ord `= 32> guard: that filters out control characters (which arrive with `id` in the 1–26 range) so Ctrl+X combos aren't mistaken for typed input.
Checking modifiers
------------------
Use `has-modifier` to test for a specific modifier key:
```raku
if $ev.id == NCKEY_ENTER && $ev.has-modifier(Mod-Ctrl) {
self!submit; # Ctrl+Enter submits
return True;
} elsif $ev.id == NCKEY_ENTER {
self!insert-newline; # plain Enter inserts a newline
return True;
}
```
Modifiers are normalised across keyboard encodings before they reach you, so the test above means the same thing on every terminal:
* Terminals speaking the **kitty keyboard protocol** (kitty, foot, ghostty, WezTerm, recent Alacritty) report every modifier explicitly, including on `Enter`, `Tab` and `Escape`.
* Terminals speaking **XTMODKEYS** / xterm's `modifyOtherKeys` report them through `CSI 27;mod;key~`.
* **Legacy** terminals transmit Alt+*key* as an `Escape` prefix and Ctrl+*letter* as a `0x01`..`0x1a` control code. Both are decoded back into `Mod-Alt` / `Mod-Ctrl` here, so `has-modifier` answers the same question either way.
The one thing legacy encodings **cannot** express is a modifier on a key that already has a control code of its own — Shift+Enter and Ctrl+Enter are indistinguishable from a plain `Enter` on such terminals, because the terminal sends the identical byte. Alt+Enter *is* distinguishable (it is `ESC` `CR`), which makes Alt the modifier to reach for when a widget needs a chord on `Enter` that works everywhere — see [Selkie::Widget::MultiLineInput](Selkie--Widget--MultiLineInput.md)'s `enter-submits` mode.
Mouse events
------------
For mouse events, `id` is one of the `NCKEY_SCROLL_UP`, `NCKEY_BUTTON1`, etc. constants, and `x`/`y` give the click coordinates:
```raku
if $ev.event-type ~~ MouseEvent {
given $ev.id {
when NCKEY_SCROLL_UP { self!scroll(-1); return True }
when NCKEY_SCROLL_DOWN { self!scroll(1); return True }
}
}
```
KEYBIND SYNTAX
==============
`Keybind.parse` and the `on-key` methods accept a string spec:
* Single character: `'a'`, `'?'`, `'Q'`, `'+'`
* Named keys: `'enter'`, `'tab'`, `'esc'` (or `'escape'`), `'space'`, `'backspace'`, `'delete'`, `'insert'`, `'home'`, `'end'`, `'pgup'`, `'pgdown'`, `'up'`, `'down'`, `'left'`, `'right'`
* Function keys: `'f1'` through `'f60'`
* Modifiers: `'ctrl+'`, `'alt+'`, `'shift+'`, `'super+'`, `'hyper+'`, `'meta+'` — combinable, e.g. `'ctrl+shift+a'`
Letter keybinds are case-insensitive — `'a'` matches both `a` and `A` (with Shift held).
The literal `'+'` key is bindable too: write it as `'+'` on its own, or as `'shift++'`, `'ctrl++'`, `'ctrl+shift++'`, etc. The parser recognises a trailing `'+'` as the key when the rest of the spec already supplies one or more modifiers.
SEE ALSO
========
* [Selkie::Widget](Selkie--Widget.md) — widgets receive events via `handle-event`
* [Selkie::App](Selkie--App.md) — the event loop dispatches to focused widget first, then parent chain, then global keybinds
Category of event. `KeyEvent` for keystrokes, `MouseEvent` for clicks and scrolls, `ResizeEvent` for terminal resizes.
Modifier keys. Test with `$ev.has-modifier(Mod-Ctrl)`, etc.
### has UInt $.id
The keycode or character codepoint of the event. For named keys this is an `NCKEY_*` constant; for printable characters it's the ordinal.
### has Str $.char
The effective printable character, if any. Respects Shift (Shift+1 → `'!'`). Undefined for non-printable keys, synthesised events, and legacy control sequences.
### has Set $.modifiers
The set of modifier keys held when the event fired. Test with `has-modifier`.
### has NcInputType $.input-type
The input type: NCTYPE_PRESS, NCTYPE_RELEASE, NCTYPE_REPEAT, etc. The framework typically filters RELEASE events before dispatching.
### has EventType $.event-type
Which category this event belongs to — see EventType.
### has Int $.y
Mouse Y coordinate for `MouseEvent`, -1 otherwise.
### has Int $.x
Mouse X coordinate for `MouseEvent`, -1 otherwise.
### has Int $.click-count
Click multiplicity for `MouseEvent` presses: 1 for a single click, 2 for a double-click, 3 for a triple-click. `0` for everything else (motion, drag, release, scroll, keyboard / resize events). Computed by `Selkie::App` from the inter-press timing and target cell — widgets read this to distinguish e.g. "select" from "open" in [Selkie::Widget::FileBrowser](Selkie--Widget--FileBrowser.md).
### method has-modifier
```raku
method has-modifier(
Modifier $mod
) returns Bool
```
True if the given modifier is part of the event's modifier set.
### method with-click-count
```raku
method with-click-count(
Int $n
) returns Selkie::Event
```
Return a fresh `Selkie::Event` identical to this one but with the given `click-count`. Used by `Selkie::App`'s mouse dispatcher to annotate a press event with its multiplicity before delivery.
### method has-any-modifier
```raku
method has-any-modifier() returns Bool
```
True if any modifier is held. Useful for "pass bare keys to the widget, bubble modified keys to global keybinds" branches.
### method from-ncinput
```raku
method from-ncinput(
Notcurses::Native::Types::Ncinput $ni
) returns Selkie::Event
```
Build a `Selkie::Event` from a raw notcurses `Ncinput` struct. Called by `Selkie::App` inside the event loop — you don't normally call this yourself. Handles: resize detection, mouse vs key classification, modifier bit decoding, effective character resolution for Shift + key combos, legacy Ctrl+A..Z control-code remapping and legacy ESC-prefix Alt for terminals without the kitty keyboard protocol.
Keybind
=======
A parsed keybind specification, produced by `Keybind.parse` and matched against events via `matches`. You don't normally construct or match these yourself — `on-key` does it for you — but the class is exposed so advanced code can inspect registered binds.
### has UInt $.id
The target keycode / character codepoint.
### has Str $.char
The target character, if the bind was for a single character.
### has Set $.modifiers
The modifier set that must be held for a match.
### has Str $.spec
The original spec string the bind was parsed from. Useful for help-overlay rendering ("Ctrl+L — Lorebooks").
### has Str $.description
Optional human-readable description of what the bind does. Set via the `:description` arg on `Widget.on-key`; surfaced by [Selkie::Widget::HelpOverlay](Selkie--Widget--HelpOverlay.md).
### has Callable &.handler
The handler callable invoked on match.
### method parse
```raku
method parse(
Str:D $spec,
&handler,
Str :$description = ""
) returns Selkie::Event::Keybind
```
Parse a keybind spec string into a `Keybind`. Spec grammar is described under KEYBIND SYNTAX in this module's main pod. Throws on unknown modifiers or unknown key names.
### method matches
```raku
method matches(
Selkie::Event $ev
) returns Bool
```
Does the given event match this keybind? Letter binds are case-insensitive — `'a'` matches a typed `A` with Shift held. All other binds require an exact modifier-set match.
MouseHandler
============
A registered click / scroll / drag / mouse-down / mouse-up handler, produced by the `on-click` / `on-scroll` / etc. methods on [Selkie::Widget](Selkie--Widget.md). Surfaced via `mouse-handlers` so help overlays can list registered mouse interactions alongside keyboard binds. Apps don't normally construct these directly.
### has Str $.kind
The kind of event this handler responds to: one of `'click'`, `'scroll'`, `'drag'`, `'mouse-down'`, `'mouse-up'`.
### has UInt $.button
The button this handler fires on (1=primary, 2=middle, 3=right). `0` is the wildcard "any button" — used by `on-scroll` (where the wheel arrives as buttons 4 and 5) and by low-level handlers that want to see every press.
### has Callable &.handler
The handler callable; receives the `Selkie::Event`.
### has Str $.description
Optional human-readable description, surfaced in help overlays.
### sub mouse-event-kinds
```raku
sub mouse-event-kinds(
Selkie::Event $ev
) returns List
```
Classify a mouse event into the list of `MouseHandler.kind`s it should fan out to. A press fires both `'click'` and `'mouse-down'` handlers; a release fires `'mouse-up'`; drag motion fires `'drag'`; scroll wheel fires `'scroll'`. Empty list for events that don't match any kind (e.g. unknown id).
### sub mouse-event-button
```raku
sub mouse-event-button(
Selkie::Event $ev
) returns UInt
```
Extract the 1-indexed button number from a mouse event id. Scroll events are buttons 4 and 5 by encoding; pure motion (NCKEY_MOTION) has no associated button and returns `0`.