Numeric keypad spans the full keyboard height

- Sections take "fillHeight": their rows stretch over the whole key area
  instead of sitting at the bottom at the shared row height
- Rows of other blocks are unaffected; the template turns it on for the numpad

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 22:31:42 +02:00
co-authored by Claude Opus 5.5
parent 2e418bd1f7
commit 75bbe2e566
8 changed files with 49 additions and 22 deletions
+14 -6
View File
@@ -8,7 +8,7 @@ namespace {
qreal blockUnits(const Block &block)
{
qreal units = 0;
for (const Row &row : block)
for (const Row &row : block.rows)
units = std::max(units, rowUnits(row));
return units;
}
@@ -44,9 +44,13 @@ qreal totalUnits(const QList<Block> &blocks)
qsizetype totalRows(const QList<Block> &blocks)
{
// Filling blocks only count when nothing else is shown.
const bool allFill = std::ranges::all_of(blocks, &Block::fillHeight);
qsizetype rows = 0;
for (const Block &block : blocks)
rows = std::max(rows, block.size());
for (const Block &block : blocks) {
if (allFill || !block.fillHeight)
rows = std::max(rows, block.rows.size());
}
return rows;
}
@@ -65,13 +69,17 @@ QList<QRectF> computeKeyRects(const QList<Block> &blocks, QSizeF size, KeyboardM
// Each cell includes one trailing gap; the last gap of a row is dropped,
// and blocks are separated by blockGap instead.
const qreal unitWidth = (innerWidth + blockCount * metrics.gap - (blockCount - 1) * metrics.blockGap) / units;
const qreal rowHeight = (innerHeight - qreal(maxRows - 1) * metrics.gap) / qreal(maxRows);
const auto heightFor = [&](qsizetype rows) { return (innerHeight - qreal(rows - 1) * metrics.gap) / qreal(rows); };
const qreal sharedRowHeight = heightFor(maxRows);
qreal left = metrics.padding;
for (const Block &block : blocks) {
const qreal width = blockUnits(block);
qreal y = metrics.padding + qreal(maxRows - block.size()) * (rowHeight + metrics.gap);
for (const Row &row : block) {
const qsizetype rows = block.rows.size();
const bool fill = block.fillHeight && rows > 0;
const qreal rowHeight = fill ? heightFor(rows) : sharedRowHeight;
qreal y = metrics.padding + (fill ? 0 : qreal(maxRows - rows) * (rowHeight + metrics.gap));
for (const Row &row : block.rows) {
const QList<qreal> widths = stretchedWidths(row, width);
const qreal used = std::accumulate(widths.begin(), widths.end(), 0.0);
qreal x = left + (width - used) * unitWidth / 2;
+9 -6
View File
@@ -12,22 +12,25 @@ struct KeyboardMetrics {
};
// The keys on screen: blocks of rows, side by side.
using Block = QList<Row>;
struct Block {
QList<Row> rows;
bool fillHeight = false; // rows grow to span the full height instead of sharing the row height
};
// Lays out blocks to fill `size`. Returns one rect per key, block by block in
// row order (spacers included). One key unit has the same width everywhere;
// all rows share one height, and shorter blocks sit at the bottom. In each
// block, stretch keys grow to make their row as wide as the widest one, and
// remaining shorter rows are centered.
// rows share one height, set by the tallest block that doesn't fill, and
// shorter blocks sit at the bottom. In each block, stretch keys grow to make
// their row as wide as the widest one, and remaining shorter rows are centered.
QList<QRectF> computeKeyRects(const QList<Block> &blocks, QSizeF size, KeyboardMetrics metrics = {});
inline QList<QRectF> computeKeyRects(const Layer &layer, QSizeF size, KeyboardMetrics metrics = {})
{
return computeKeyRects({layer.rows}, size, metrics);
return computeKeyRects({{layer.rows}}, size, metrics);
}
// Total width of a row in key units, before stretching.
qreal rowUnits(const Row &row);
// Width of all blocks in key units, ignoring gaps.
qreal totalUnits(const QList<Block> &blocks);
// Rows of the tallest block.
// Rows of the tallest block that sets the row height (see computeKeyRects).
qsizetype totalRows(const QList<Block> &blocks);
+1
View File
@@ -75,6 +75,7 @@ struct Section {
QString id; // "main" is reserved for the layers
QString name; // shown in settings
Placement placement = Placement::Right;
bool fillHeight = false; // a right-hand block whose rows span the whole keyboard height
QList<Row> rows;
};
+1
View File
@@ -161,6 +161,7 @@ std::optional<Section> parseSection(const QJsonObject &obj, QString *error)
return std::nullopt;
}
section.placement = placement == u"above" ? Section::Placement::Above : Section::Placement::Right;
section.fillHeight = obj.value(u"fillHeight").toBool(false);
if (!parseRows(obj.value(u"rows"), &section.rows, error)) {
*error = QStringLiteral("section \"%1\": %2").arg(section.id, *error);
return std::nullopt;
+7 -7
View File
@@ -181,21 +181,21 @@ QList<Block> LayoutModel::composeBlocks() const
Block main;
for (const Section &section : m_layout.sections) {
if (section.placement == Section::Placement::Above && shown(section.id))
main += section.rows;
main.rows += section.rows;
}
if (layer && shown(MainSectionId))
main += layer->rows;
main.rows += layer->rows;
QList<Block> blocks;
if (!main.isEmpty())
if (!main.rows.isEmpty())
blocks.append(main);
for (const Section &section : m_layout.sections) {
if (section.placement == Section::Placement::Right && shown(section.id))
blocks.append(section.rows);
blocks.append({section.rows, section.fillHeight});
}
// Never show nothing.
if (blocks.isEmpty() && layer)
blocks.append(layer->rows);
blocks.append({layer->rows});
return blocks;
}
@@ -205,7 +205,7 @@ void LayoutModel::rebuild()
m_blocks = composeBlocks();
m_keys.clear();
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block) {
for (const Row &row : block.rows) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_keys.append(key);
@@ -227,7 +227,7 @@ void LayoutModel::relayout()
const QList<QRectF> all = computeKeyRects(m_blocks, m_size);
qsizetype i = 0;
for (const Block &block : std::as_const(m_blocks)) {
for (const Row &row : block) {
for (const Row &row : block.rows) {
for (const KeyDef &key : row) {
if (key.type != KeyType::Spacer)
m_rects.append(all[i]);