mirror of
https://github.com/zer0condition/GoodmansKernel/
synced 2026-08-19 01:14:35 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
#include "command_palette.h"
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
// Fuzzy sort proxy: match if all chars in query appear in order in target.
|
||||
// Scoring: fewer gaps + earlier match wins. Used to power the "type anywhere" flow.
|
||||
class FuzzyProxy : public QSortFilterProxyModel {
|
||||
public:
|
||||
explicit FuzzyProxy(QObject* p = nullptr) : QSortFilterProxyModel(p) { setDynamicSortFilter(true); }
|
||||
void setQuery(const QString& q) { query = q.toLower(); invalidateFilter(); invalidate(); }
|
||||
protected:
|
||||
bool filterAcceptsRow(int row, const QModelIndex&) const override {
|
||||
if (query.isEmpty()) return true;
|
||||
QString s = sourceModel()->data(sourceModel()->index(row, 0)).toString().toLower();
|
||||
int qi = 0;
|
||||
for (int i = 0; i < s.size() && qi < query.size(); i++) if (s[i] == query[qi]) qi++;
|
||||
return qi == query.size();
|
||||
}
|
||||
bool lessThan(const QModelIndex& l, const QModelIndex& r) const override {
|
||||
if (query.isEmpty()) return l.row() < r.row();
|
||||
auto score = [&](const QString& s)->int {
|
||||
int qi = 0, first = -1, gaps = 0, prev = -100;
|
||||
QString low = s.toLower();
|
||||
for (int i = 0; i < low.size() && qi < query.size(); i++) {
|
||||
if (low[i] == query[qi]) {
|
||||
if (first == -1) first = i;
|
||||
if (prev != i - 1) gaps++;
|
||||
prev = i; qi++;
|
||||
}
|
||||
}
|
||||
return first * 4 + gaps * 8 + s.size();
|
||||
};
|
||||
int sl = score(sourceModel()->data(l).toString());
|
||||
int sr = score(sourceModel()->data(r).toString());
|
||||
return sl < sr;
|
||||
}
|
||||
private:
|
||||
QString query;
|
||||
};
|
||||
|
||||
class PaletteDelegate : public QStyledItemDelegate {
|
||||
public:
|
||||
using QStyledItemDelegate::QStyledItemDelegate;
|
||||
QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const override { return {0, 34}; }
|
||||
void paint(QPainter* p, const QStyleOptionViewItem& o, const QModelIndex& idx) const override {
|
||||
p->save();
|
||||
if (o.state & QStyle::State_Selected) p->fillRect(o.rect, QColor(0x2a, 0x21, 0x17));
|
||||
auto full = idx.data().toString();
|
||||
auto hint = idx.data(Qt::UserRole + 1).toString();
|
||||
p->setPen((o.state & QStyle::State_Selected) ? QColor(0xf0, 0xa3, 0x40) : QColor(0xd4, 0xd7, 0xdd));
|
||||
QFont f = p->font(); f.setPointSizeF(11.5); p->setFont(f);
|
||||
p->drawText(QRect(o.rect.left() + 16, o.rect.top(), o.rect.width() - 200, o.rect.height()),
|
||||
Qt::AlignVCenter | Qt::AlignLeft, full);
|
||||
if (!hint.isEmpty()) {
|
||||
p->setPen(QColor(0x6a, 0x72, 0x80));
|
||||
QFont hf = f; hf.setPointSizeF(10.5); hf.setFamily("JetBrainsMono NF"); p->setFont(hf);
|
||||
p->drawText(QRect(o.rect.right() - 180, o.rect.top(), 164, o.rect.height()),
|
||||
Qt::AlignVCenter | Qt::AlignRight, hint);
|
||||
}
|
||||
p->restore();
|
||||
}
|
||||
};
|
||||
|
||||
CommandPalette::CommandPalette(QWidget* parent, const QVector<CommandAction>& actions)
|
||||
: QDialog(parent), all_actions(actions)
|
||||
{
|
||||
setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
|
||||
setAttribute(Qt::WA_TranslucentBackground, false);
|
||||
setStyleSheet(
|
||||
"QDialog { background: #14171c; border: 1px solid #363c47; border-radius: 10px; }"
|
||||
"QLineEdit { background: transparent; border: none; border-bottom: 1px solid #262a33;"
|
||||
" padding: 14px 18px; font-size: 15px; color: #f0f2f7; }"
|
||||
"QListView { background: transparent; border: none; outline: 0; }"
|
||||
);
|
||||
|
||||
resize(640, 420);
|
||||
|
||||
auto* l = new QVBoxLayout(this);
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->setSpacing(0);
|
||||
|
||||
search = new QLineEdit;
|
||||
search->setPlaceholderText("Type a command…");
|
||||
l->addWidget(search);
|
||||
|
||||
model = new QStandardItemModel(this);
|
||||
for (const auto& a : all_actions) {
|
||||
auto* it = new QStandardItem(a.label);
|
||||
it->setData(a.hint, Qt::UserRole + 1);
|
||||
model->appendRow(it);
|
||||
}
|
||||
proxy = new FuzzyProxy(this);
|
||||
proxy->setSourceModel(model);
|
||||
|
||||
list = new QListView;
|
||||
list->setModel(proxy);
|
||||
list->setItemDelegate(new PaletteDelegate(this));
|
||||
list->setUniformItemSizes(true);
|
||||
list->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
l->addWidget(list, 1);
|
||||
|
||||
if (proxy->rowCount() > 0)
|
||||
list->setCurrentIndex(proxy->index(0, 0));
|
||||
|
||||
connect(search, &QLineEdit::textChanged, this, [this](const QString& t) {
|
||||
static_cast<FuzzyProxy*>(proxy)->setQuery(t);
|
||||
proxy->sort(0);
|
||||
if (proxy->rowCount() > 0) list->setCurrentIndex(proxy->index(0, 0));
|
||||
});
|
||||
connect(list, &QListView::activated, this, [this](const QModelIndex&) { runSelected(); });
|
||||
|
||||
search->setFocus();
|
||||
}
|
||||
|
||||
void CommandPalette::runSelected()
|
||||
{
|
||||
auto idx = list->currentIndex();
|
||||
if (!idx.isValid()) return;
|
||||
auto src = proxy->mapToSource(idx);
|
||||
int i = src.row();
|
||||
if (i < 0 || i >= all_actions.size()) return;
|
||||
auto fn = all_actions[i].run;
|
||||
accept();
|
||||
if (fn) fn();
|
||||
}
|
||||
|
||||
void CommandPalette::keyPressEvent(QKeyEvent* e)
|
||||
{
|
||||
if (e->key() == Qt::Key_Escape) { reject(); return; }
|
||||
if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { runSelected(); return; }
|
||||
if (e->key() == Qt::Key_Up) {
|
||||
auto cur = list->currentIndex().row();
|
||||
if (cur > 0) list->setCurrentIndex(proxy->index(cur - 1, 0));
|
||||
return;
|
||||
}
|
||||
if (e->key() == Qt::Key_Down) {
|
||||
auto cur = list->currentIndex().row();
|
||||
if (cur < proxy->rowCount() - 1) list->setCurrentIndex(proxy->index(cur + 1, 0));
|
||||
return;
|
||||
}
|
||||
QDialog::keyPressEvent(e);
|
||||
}
|
||||
Reference in New Issue
Block a user