- 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>
239 lines
6.9 KiB
C++
239 lines
6.9 KiB
C++
#include "LayoutModel.h"
|
|
|
|
#include <limits>
|
|
|
|
namespace {
|
|
|
|
QString keyTypeName(KeyType type)
|
|
{
|
|
switch (type) {
|
|
case KeyType::Character: return QStringLiteral("char");
|
|
case KeyType::Space: return QStringLiteral("space");
|
|
case KeyType::Backspace: return QStringLiteral("backspace");
|
|
case KeyType::Enter: return QStringLiteral("enter");
|
|
case KeyType::Shift: return QStringLiteral("shift");
|
|
case KeyType::CapsLock: return QStringLiteral("capslock");
|
|
case KeyType::Modifier: return QStringLiteral("modifier");
|
|
case KeyType::LayerSwitch: return QStringLiteral("layer");
|
|
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:
|
|
case KeyType::Keys: break; // expanded before it reaches the model
|
|
}
|
|
return {};
|
|
}
|
|
|
|
qreal distanceSquared(const QRectF &rect, QPointF p)
|
|
{
|
|
const qreal dx = std::max({rect.left() - p.x(), 0.0, p.x() - rect.right()});
|
|
const qreal dy = std::max({rect.top() - p.y(), 0.0, p.y() - rect.bottom()});
|
|
return dx * dx + dy * dy;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int LayoutModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
return parent.isValid() ? 0 : int(m_keys.size());
|
|
}
|
|
|
|
QVariant LayoutModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
|
|
return {};
|
|
|
|
const KeyDef &key = m_keys.at(index.row());
|
|
const QRectF rect = m_rects.value(index.row());
|
|
switch (role) {
|
|
case LabelRole: return key.text(m_shiftLevel);
|
|
// A shifted key already shows its shifted symbol (! on 1): don't repeat it.
|
|
case FlickLabelRole: return m_shiftLevel == ShiftLevel::Shifted && key.flick == key.shifted ? QString() : key.flick;
|
|
case ModifierRole: return key.modifier;
|
|
case KeyTypeRole: return keyTypeName(key.type);
|
|
case KeyXRole: return rect.x();
|
|
case KeyYRole: return rect.y();
|
|
case KeyWidthRole: return rect.width();
|
|
case KeyHeightRole: return rect.height();
|
|
case PressedRole: return m_pressCount.at(index.row()) > 0;
|
|
case FlickProgressRole: return m_flickProgress.at(index.row());
|
|
}
|
|
return {};
|
|
}
|
|
|
|
QHash<int, QByteArray> LayoutModel::roleNames() const
|
|
{
|
|
return {
|
|
{LabelRole, "label"},
|
|
{FlickLabelRole, "flickLabel"},
|
|
{KeyTypeRole, "keyType"},
|
|
{KeyXRole, "keyX"},
|
|
{KeyYRole, "keyY"},
|
|
{KeyWidthRole, "keyWidth"},
|
|
{KeyHeightRole, "keyHeight"},
|
|
{PressedRole, "pressed"},
|
|
{FlickProgressRole, "flickProgress"},
|
|
{ModifierRole, "modifier"},
|
|
};
|
|
}
|
|
|
|
void LayoutModel::setLayout(const Layout &layout)
|
|
{
|
|
m_layout = layout;
|
|
m_layerName = layout.layers.isEmpty() ? QString() : layout.layers.first().name;
|
|
rebuild();
|
|
Q_EMIT layoutChanged();
|
|
Q_EMIT layerChanged();
|
|
}
|
|
|
|
void LayoutModel::setLayer(const QString &layerName)
|
|
{
|
|
if (layerName == m_layerName || !m_layout.layer(layerName))
|
|
return;
|
|
m_layerName = layerName;
|
|
rebuild();
|
|
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 §ion : m_layout.sections)
|
|
list.append(QVariantMap{{QStringLiteral("id"), section.id}, {QStringLiteral("name"), section.name}});
|
|
return list;
|
|
}
|
|
|
|
qreal LayoutModel::totalUnits() const
|
|
{
|
|
return ::totalUnits(m_blocks);
|
|
}
|
|
|
|
qsizetype LayoutModel::totalRows() const
|
|
{
|
|
return ::totalRows(m_blocks);
|
|
}
|
|
|
|
void LayoutModel::setSize(QSizeF size)
|
|
{
|
|
if (size == m_size)
|
|
return;
|
|
m_size = size;
|
|
relayout();
|
|
if (!m_keys.isEmpty())
|
|
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {KeyXRole, KeyYRole, KeyWidthRole, KeyHeightRole});
|
|
}
|
|
|
|
void LayoutModel::setShiftLevel(ShiftLevel level)
|
|
{
|
|
if (level == m_shiftLevel)
|
|
return;
|
|
m_shiftLevel = level;
|
|
if (!m_keys.isEmpty())
|
|
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {LabelRole, FlickLabelRole});
|
|
}
|
|
|
|
void LayoutModel::setPressed(int row, bool pressed)
|
|
{
|
|
if (row < 0 || row >= m_pressCount.size())
|
|
return;
|
|
const bool wasPressed = m_pressCount[row] > 0;
|
|
m_pressCount[row] = std::max(0, m_pressCount[row] + (pressed ? 1 : -1));
|
|
if (wasPressed != (m_pressCount[row] > 0))
|
|
Q_EMIT dataChanged(index(row), index(row), {PressedRole});
|
|
}
|
|
|
|
void LayoutModel::setFlickProgress(int row, qreal progress)
|
|
{
|
|
if (row < 0 || row >= m_flickProgress.size() || qFuzzyCompare(m_flickProgress[row], progress))
|
|
return;
|
|
m_flickProgress[row] = progress;
|
|
Q_EMIT dataChanged(index(row), index(row), {FlickProgressRole});
|
|
}
|
|
|
|
int LayoutModel::keyAt(QPointF pos) const
|
|
{
|
|
int nearest = -1;
|
|
qreal best = std::numeric_limits<qreal>::max();
|
|
for (qsizetype i = 0; i < m_rects.size(); ++i) {
|
|
const qreal d = distanceSquared(m_rects[i], pos);
|
|
if (d < best) {
|
|
best = d;
|
|
nearest = int(i);
|
|
}
|
|
}
|
|
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 §ion : m_layout.sections) {
|
|
if (section.placement == Section::Placement::Above && shown(section.id))
|
|
main.rows += section.rows;
|
|
}
|
|
if (layer && shown(MainSectionId))
|
|
main.rows += layer->rows;
|
|
|
|
QList<Block> blocks;
|
|
if (!main.rows.isEmpty())
|
|
blocks.append(main);
|
|
for (const Section §ion : m_layout.sections) {
|
|
if (section.placement == Section::Placement::Right && shown(section.id))
|
|
blocks.append({section.rows, section.fillHeight});
|
|
}
|
|
// Never show nothing.
|
|
if (blocks.isEmpty() && layer)
|
|
blocks.append({layer->rows});
|
|
return blocks;
|
|
}
|
|
|
|
void LayoutModel::rebuild()
|
|
{
|
|
beginResetModel();
|
|
m_blocks = composeBlocks();
|
|
m_keys.clear();
|
|
for (const Block &block : std::as_const(m_blocks)) {
|
|
for (const Row &row : block.rows) {
|
|
for (const KeyDef &key : row) {
|
|
if (key.type != KeyType::Spacer)
|
|
m_keys.append(key);
|
|
}
|
|
}
|
|
}
|
|
m_pressCount = QList<int>(m_keys.size(), 0);
|
|
m_flickProgress = QList<qreal>(m_keys.size(), 0.0);
|
|
relayout();
|
|
endResetModel();
|
|
}
|
|
|
|
void LayoutModel::relayout()
|
|
{
|
|
m_rects.clear();
|
|
if (m_size.isEmpty())
|
|
return;
|
|
|
|
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.rows) {
|
|
for (const KeyDef &key : row) {
|
|
if (key.type != KeyType::Spacer)
|
|
m_rects.append(all[i]);
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|