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:
@@ -0,0 +1,128 @@
|
||||
#include "engine/InputEngine.h"
|
||||
#include "input/KeyboardController.h"
|
||||
#include "layout/LayoutLoader.h"
|
||||
#include "layout/LayoutModel.h"
|
||||
#include "layout/LayoutRepository.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace {
|
||||
|
||||
class FakeEngine : public InputEngine
|
||||
{
|
||||
public:
|
||||
void commitText(const QString &text) override { output += text; }
|
||||
void sendKeysym(quint32 keysym) override { keysyms.append(keysym); }
|
||||
|
||||
QString output;
|
||||
QList<quint32> keysyms;
|
||||
};
|
||||
|
||||
KeyDef makeKey(KeyType type, const QString &label = {})
|
||||
{
|
||||
KeyDef k;
|
||||
k.type = type;
|
||||
k.label = label;
|
||||
return k;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class TestController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
FakeEngine engine;
|
||||
LayoutRepository repo;
|
||||
LayoutModel model;
|
||||
std::unique_ptr<KeyboardController> controller;
|
||||
qint64 now = 0;
|
||||
|
||||
void tap(const KeyDef &key, KeyOutput output = KeyOutput::Primary, const QString &alternate = {})
|
||||
{
|
||||
now += 1000; // far apart: never a double tap
|
||||
controller->keyDown(key, now);
|
||||
controller->keyUp(key, output, alternate, now + 50);
|
||||
}
|
||||
|
||||
private Q_SLOTS:
|
||||
void init()
|
||||
{
|
||||
engine = {};
|
||||
repo = {};
|
||||
Q_INIT_RESOURCE(layouts);
|
||||
repo.loadDirectory(u":/layouts"_s);
|
||||
repo.finalize(u"en_US"_s);
|
||||
controller = std::make_unique<KeyboardController>(engine, model, repo);
|
||||
}
|
||||
|
||||
void typesLowercase()
|
||||
{
|
||||
tap(makeKey(KeyType::Character, u"a"_s));
|
||||
QCOMPARE(engine.output, u"a");
|
||||
}
|
||||
|
||||
void shiftCapitalizesOneCharacter()
|
||||
{
|
||||
const KeyDef a = makeKey(KeyType::Character, u"a"_s);
|
||||
tap(makeKey(KeyType::Shift));
|
||||
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Once);
|
||||
tap(a);
|
||||
tap(a);
|
||||
QCOMPARE(engine.output, u"Aa");
|
||||
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Off);
|
||||
}
|
||||
|
||||
void flickAndAlternateOutputs()
|
||||
{
|
||||
KeyDef e = makeKey(KeyType::Character, u"e"_s);
|
||||
e.flick = u"3"_s;
|
||||
tap(e, KeyOutput::Flick);
|
||||
tap(e, KeyOutput::Alternate, u"é"_s);
|
||||
QCOMPARE(engine.output, u"3é");
|
||||
}
|
||||
|
||||
void backspaceActsOnPress()
|
||||
{
|
||||
controller->keyDown(makeKey(KeyType::Backspace), 0);
|
||||
QCOMPARE(engine.keysyms, QList<quint32>{XKB_KEY_BackSpace});
|
||||
}
|
||||
|
||||
void enterSendsReturn()
|
||||
{
|
||||
tap(makeKey(KeyType::Enter));
|
||||
QCOMPARE(engine.keysyms, QList<quint32>{XKB_KEY_Return});
|
||||
}
|
||||
|
||||
void layerSwitchChangesModelLayer()
|
||||
{
|
||||
KeyDef layer = makeKey(KeyType::LayerSwitch);
|
||||
layer.target = u"numbers"_s;
|
||||
controller->keyDown(layer, 0);
|
||||
QCOMPARE(model.layerName(), u"numbers");
|
||||
}
|
||||
|
||||
void globeCyclesLayouts()
|
||||
{
|
||||
QCOMPARE(model.layoutName(), u"English (US)");
|
||||
tap(makeKey(KeyType::Globe));
|
||||
QCOMPARE(model.layoutName(), u"Svenska");
|
||||
tap(makeKey(KeyType::Globe));
|
||||
QCOMPARE(model.layoutName(), u"English (US)");
|
||||
}
|
||||
|
||||
void dismissEmitsSignal()
|
||||
{
|
||||
int dismissed = 0;
|
||||
connect(controller.get(), &KeyboardController::dismissRequested, this, [&] { ++dismissed; });
|
||||
tap(makeKey(KeyType::Dismiss));
|
||||
QCOMPARE(dismissed, 1);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestController)
|
||||
#include "tst_controller.moc"
|
||||
Reference in New Issue
Block a user