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,108 @@
|
||||
#include "KeyboardController.h"
|
||||
|
||||
#include "engine/InputEngine.h"
|
||||
#include "layout/LayoutModel.h"
|
||||
#include "layout/LayoutRepository.h"
|
||||
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
|
||||
// ShiftMode mirrors ShiftState::Mode for QML.
|
||||
static_assert(int(KeyboardController::ShiftMode::Off) == int(ShiftState::Mode::Off));
|
||||
static_assert(int(KeyboardController::ShiftMode::Once) == int(ShiftState::Mode::Once));
|
||||
static_assert(int(KeyboardController::ShiftMode::Locked) == int(ShiftState::Mode::Locked));
|
||||
|
||||
KeyboardController::KeyboardController(InputEngine &engine, LayoutModel &model,
|
||||
const LayoutRepository &layouts, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_engine(engine)
|
||||
, m_model(model)
|
||||
, m_layouts(layouts)
|
||||
{
|
||||
if (!m_layouts.layouts().isEmpty())
|
||||
m_model.setLayout(m_layouts.layouts().first());
|
||||
}
|
||||
|
||||
KeyboardController::ShiftMode KeyboardController::shiftMode() const
|
||||
{
|
||||
return ShiftMode(m_shift.mode());
|
||||
}
|
||||
|
||||
void KeyboardController::keyDown(const KeyDef &key, qint64 timeMs)
|
||||
{
|
||||
switch (key.type) {
|
||||
case KeyType::Shift:
|
||||
changeShift([&] { m_shift.press(timeMs); });
|
||||
break;
|
||||
case KeyType::Backspace:
|
||||
m_engine.sendKeysym(XKB_KEY_BackSpace);
|
||||
break;
|
||||
case KeyType::LayerSwitch:
|
||||
m_model.setLayer(key.target);
|
||||
break;
|
||||
default:
|
||||
break; // everything else acts on release
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QString &alternate, qint64)
|
||||
{
|
||||
switch (key.type) {
|
||||
case KeyType::Shift:
|
||||
changeShift([&] { m_shift.release(); });
|
||||
break;
|
||||
case KeyType::Character:
|
||||
if (output == KeyOutput::Alternate && !alternate.isEmpty())
|
||||
typeText(alternate);
|
||||
else if (output == KeyOutput::Flick && !key.flick.isEmpty())
|
||||
typeText(key.flick);
|
||||
else
|
||||
typeText(key.text(m_shift.isUppercase()));
|
||||
break;
|
||||
case KeyType::Space:
|
||||
typeText(QStringLiteral(" "));
|
||||
break;
|
||||
case KeyType::Enter:
|
||||
m_engine.sendKeysym(XKB_KEY_Return);
|
||||
break;
|
||||
case KeyType::Globe:
|
||||
nextLayout();
|
||||
break;
|
||||
case KeyType::Dismiss:
|
||||
Q_EMIT dismissRequested();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardController::keyCancelled(const KeyDef &key)
|
||||
{
|
||||
if (key.type == KeyType::Shift)
|
||||
changeShift([&] { m_shift.release(); });
|
||||
}
|
||||
|
||||
void KeyboardController::nextLayout()
|
||||
{
|
||||
const auto &layouts = m_layouts.layouts();
|
||||
if (layouts.size() < 2)
|
||||
return;
|
||||
m_layoutIndex = int((m_layoutIndex + 1) % layouts.size());
|
||||
m_model.setLayout(layouts[m_layoutIndex]);
|
||||
}
|
||||
|
||||
void KeyboardController::typeText(const QString &text)
|
||||
{
|
||||
m_engine.commitText(text);
|
||||
changeShift([&] { m_shift.characterTyped(); });
|
||||
}
|
||||
|
||||
template<typename Change>
|
||||
void KeyboardController::changeShift(Change change)
|
||||
{
|
||||
const ShiftState::Mode before = m_shift.mode();
|
||||
change();
|
||||
if (m_shift.mode() == before)
|
||||
return;
|
||||
m_model.setUppercase(m_shift.isUppercase());
|
||||
Q_EMIT shiftChanged();
|
||||
}
|
||||
Reference in New Issue
Block a user