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
+46
View File
@@ -0,0 +1,46 @@
#include "KeyGeometry.h"
#include <algorithm>
namespace {
qreal rowUnits(const Row &row)
{
qreal units = 0;
for (const KeyDef &key : row)
units += key.width;
return units;
}
} // namespace
QList<QRectF> computeKeyRects(const Layer &layer, QSizeF size, KeyboardMetrics metrics)
{
QList<QRectF> rects;
if (layer.rows.isEmpty())
return rects;
qreal maxUnits = 0;
for (const Row &row : layer.rows)
maxUnits = std::max(maxUnits, rowUnits(row));
const qreal innerWidth = size.width() - 2 * metrics.padding;
const qreal innerHeight = size.height() - 2 * metrics.padding;
const int rowCount = int(layer.rows.size());
// Each cell includes one trailing gap; the last gap of a row is dropped.
const qreal unitWidth = (innerWidth + metrics.gap) / maxUnits;
const qreal rowHeight = (innerHeight - (rowCount - 1) * metrics.gap) / rowCount;
qreal y = metrics.padding;
for (const Row &row : layer.rows) {
qreal x = metrics.padding + (maxUnits - rowUnits(row)) * unitWidth / 2;
for (const KeyDef &key : row) {
const qreal cellWidth = key.width * unitWidth;
rects.append(QRectF(x, y, std::max<qreal>(0, cellWidth - metrics.gap), rowHeight));
x += cellWidth;
}
y += rowHeight + metrics.gap;
}
return rects;
}