Layouts, iPad-style rendering, taps, shift and layers

- JSON layouts (en-US, sv-SE inheriting number/symbol layers via base)
- LayoutModel + KeyGeometry: proportional rows, nearest-key hit testing
- KeyboardSurface: multi-pointer touch/mouse routing to the controller
- KeyboardController + ShiftState: iOS shift/caps-lock, layers, globe, dismiss
- QML view with light/dark iPad palette and drag handle
- DevHooks: SLOPOSK_AUTOTYPE, SLOPOSK_SCREENSHOT for headless checks
- Unit tests for shift, loader, geometry and controller

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 14:50:21 +02:00
co-authored by Claude Opus 5.5
parent cdef9b4ad0
commit be14bbcc62
35 changed files with 1970 additions and 65 deletions
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <QList>
#include <QString>
#include <QStringList>
enum class KeyType {
Character,
Space,
Backspace,
Enter,
Shift,
LayerSwitch,
Globe,
Dismiss,
Spacer, // empty room, e.g. the half-key indent of the home row
};
struct KeyDef {
KeyType type = KeyType::Character;
QString label; // shown and typed (for character keys)
QString shifted; // explicit shifted form; empty means label.toUpper()
QString flick; // secondary symbol, typed by flicking down
QStringList alternates; // long-press choices
QString target; // layer to switch to (LayerSwitch only)
qreal width = 1.0; // in key units
// Text this key shows/types; only character keys change case.
QString text(bool uppercase) const
{
if (!uppercase || type != KeyType::Character)
return label;
return shifted.isEmpty() ? label.toUpper() : shifted;
}
};
using Row = QList<KeyDef>;
struct Layer {
QString name;
QList<Row> rows;
};
struct Layout {
QString id; // e.g. "en-US"
QString name; // human readable
QString locale; // e.g. "en_US"
QString base; // id of a layout to take missing layers from
QList<Layer> layers; // the first one is shown by default
const Layer *layer(const QString &layerName) const
{
for (const Layer &l : layers) {
if (l.name == layerName)
return &l;
}
return nullptr;
}
};