Optional PC key blocks: function row, navigation, numeric keypad

- Template sections placed above or to the right of the typing keys;
  the user picks any mix in settings, typing keys included
- keysym keys send XKB keysyms on press, optionally repeating
- Geometry lays blocks side by side with one key unit; stretch keys are
  resolved at layout time so composed blocks line up
- Toggling a block resizes the window so keys keep their size

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 21:18:10 +02:00
co-authored by Claude Opus 5.5
parent e532f5c072
commit 88338fde22
22 changed files with 521 additions and 101 deletions
+60 -10
View File
@@ -16,6 +16,7 @@ QString keyTypeName(KeyType type)
case KeyType::Globe: return QStringLiteral("globe");
case KeyType::Dismiss: return QStringLiteral("dismiss");
case KeyType::Spacer: return QStringLiteral("spacer");
case KeyType::Keysym: return QStringLiteral("keysym");
case KeyType::Letters: break; // expanded before it reaches the model
}
return {};
@@ -89,6 +90,27 @@ void LayoutModel::setLayer(const QString &layerName)
Q_EMIT layerChanged();
}
void LayoutModel::setVisibleSections(const QStringList &ids)
{
if (ids == m_visibleSections)
return;
m_visibleSections = ids;
rebuild();
}
QVariantList LayoutModel::sections() const
{
QVariantList list;
for (const Section &section : m_layout.sections)
list.append(QVariantMap{{QStringLiteral("id"), section.id}, {QStringLiteral("name"), section.name}});
return list;
}
qreal LayoutModel::totalUnits() const
{
return ::totalUnits(m_blocks);
}
void LayoutModel::setSize(QSizeF size)
{
if (size == m_size)
@@ -140,12 +162,39 @@ int LayoutModel::keyAt(QPointF pos) const
return nearest;
}
QList<Block> LayoutModel::composeBlocks() const
{
const auto shown = [this](const QString &id) { return m_visibleSections.contains(id); };
const Layer *layer = m_layout.layer(m_layerName);
Block main;
for (const Section &section : m_layout.sections) {
if (section.placement == Section::Placement::Above && shown(section.id))
main += section.rows;
}
if (layer && shown(MainSectionId))
main += layer->rows;
QList<Block> blocks;
if (!main.isEmpty())
blocks.append(main);
for (const Section &section : m_layout.sections) {
if (section.placement == Section::Placement::Right && shown(section.id))
blocks.append(section.rows);
}
// Never show nothing.
if (blocks.isEmpty() && layer)
blocks.append(layer->rows);
return blocks;
}
void LayoutModel::rebuild()
{
beginResetModel();
m_blocks = composeBlocks();
m_keys.clear();
if (const Layer *layer = m_layout.layer(m_layerName)) {
for (const Row &row : layer->rows) {
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_keys.append(key);
@@ -161,17 +210,18 @@ void LayoutModel::rebuild()
void LayoutModel::relayout()
{
m_rects.clear();
const Layer *layer = m_layout.layer(m_layerName);
if (!layer || m_size.isEmpty())
if (m_size.isEmpty())
return;
const QList<QRectF> all = computeKeyRects(*layer, m_size);
const QList<QRectF> all = computeKeyRects(m_blocks, m_size);
qsizetype i = 0;
for (const Row &row : layer->rows) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_rects.append(all[i]);
++i;
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_rects.append(all[i]);
++i;
}
}
}
}