follow cursor, spacer rows, shade rows within fold

portability/macos
zetaPRIME 2019-07-20 01:09:59 -04:00
parent 707a6169a2
commit b65ce423a7
8 changed files with 87 additions and 52 deletions

6
notes
View File

@ -32,9 +32,9 @@ parameters {
TODO {
immediate frontburner {
spacer rows on top/bottom of pattern editor (keep centered)
make ctrl+pgup/dn jump to the next *actual pattern*, looping around if necessary
project-relative export
- spacer rows on top/bottom of pattern editor (keep centered)
non-hardcoded export path
reimplement sample import to IPC from standalone ffmpeg since QAudioDecoder is partially broken on arch and completely broken on macOS
distortion effect
single-selection sampler

View File

@ -7,22 +7,23 @@ namespace Xybrid::Config {
public:
ColorScheme() = default;
QColor patternSelection = QColor(127, 63, 255, 63);
QColor patternFoldIndicator = QColor(95, 95, 95);
QColor patternSelection = {127, 63, 255, 63};
QColor patternFoldIndicator = {95, 95, 95};
QColor patternFoldShade = {0, 0, 0, 31};
QColor patternBg = QColor(23, 23, 23);
QColor patternBgBeat = QColor(31, 31, 31);
QColor patternBgMeasure = QColor(39, 39, 39);
QColor patternBg = {23, 23, 23};
QColor patternBgBeat = {31, 31, 31};
QColor patternBgMeasure = {39, 39, 39};
QColor patternFgBlank = QColor(127, 127, 127);
QColor patternFgPort = QColor(191, 191, 191);
QColor patternFgNote = QColor(255, 255, 255);
QColor patternFgParamCmd = QColor(191,163,255);
QColor patternFgParamAmt = QColor(191,222,255);
QColor patternFgBlank = {127, 127, 127};
QColor patternFgPort = {191, 191, 191};
QColor patternFgNote = {255, 255, 255};
QColor patternFgParamCmd = {191, 163, 255};
QColor patternFgParamAmt = {191, 222, 255};
QColor waveformBg = QColor(23, 23, 23);
QColor waveformBgHighlight = QColor(31, 31, 47);
QColor waveformFgPrimary = QColor(191, 163, 255);
QColor waveformBg = {23, 23, 23};
QColor waveformBgHighlight = {31, 31, 47};
QColor waveformFgPrimary = {191, 163, 255};
};
extern ColorScheme colorScheme;
}

View File

@ -541,9 +541,9 @@ int MainWindow::sequenceSelection(int n) {
void MainWindow::playbackPosition(int seq, int row) {
sequenceSelection(seq);
if (ui->patternEditor->isFolded() && editingPattern->fold > 1) row -= row % editingPattern->fold;
auto mi = ui->patternEditor->currentIndex().siblingAtRow(row);
auto mi = ui->patternEditor->currentIndex().siblingAtRow(row+1);
if (!mi.isValid()) mi = ui->patternEditor->model()->index(row, 0);
if (!mi.isValid()) mi = ui->patternEditor->model()->index(row+1, 0);
ui->patternEditor->setCurrentIndex(mi);
ui->patternEditor->selectionModel()->select(QItemSelection(mi.siblingAtColumn(0), mi.siblingAtColumn(ui->patternEditor->horizontalHeader()->count()-1)), QItemSelectionModel::SelectionFlag::ClearAndSelect);
ui->patternEditor->scrollTo(mi, QAbstractItemView::PositionAtCenter);

View File

@ -91,19 +91,21 @@ namespace {
}
void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
int row = index.row() - 1;
auto mdl = const_cast<PatternEditorModel*>(static_cast<const PatternEditorModel*>(index.model()));
auto p = mdl->getPattern();
{ /* background */ } {
painter->fillRect(option.rect, Config::colorScheme.patternBg);
if (p->fold > 1 && row >= 0 && row % p->fold != 0) painter->fillRect(option.rect, Config::colorScheme.patternFoldShade);
if (option.state & QStyle::State_Enabled) {
if (index.row() % p->time.rowsPerMeasure() == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgMeasure);
else if (index.row() % p->time.rowsPerBeat == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgBeat);
if (row % p->time.rowsPerMeasure() == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgMeasure);
else if (row % p->time.rowsPerBeat == 0) painter->fillRect(option.rect, Config::colorScheme.patternBgBeat);
}
}
// selection/cursor highlight
if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, Config::colorScheme.patternSelection);
if (option.state & QStyle::State_HasFocus) {
if (option.state & QStyle::State_HasFocus && p->channels.size() > 0) {
painter->setPen(Config::colorScheme.patternSelection);
painter->drawRect(option.rect.adjusted(0,0,-1,-1));
painter->drawRect(option.rect.adjusted(0,0,-1,-1));
@ -117,7 +119,6 @@ void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewI
int cc = index.column() % PatternEditorModel::colsPerChannel;
int ch = (index.column() - cc) / PatternEditorModel::colsPerChannel;
if (ch < static_cast<int>(p->numChannels())) {
int row = index.row();
int rowEnd = row + p->fold;
for (int i = row + 1; i < rowEnd; i++) {
QString v = index.siblingAtRow(i).data(Qt::DisplayRole).toString();
@ -178,7 +179,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
auto p = m->getPattern();
int cc = index.column() % PatternEditorModel::colsPerChannel;
int ch = (index.column() - cc) / PatternEditorModel::colsPerChannel;
auto* dc = new PatternDeltaCommand(p, ch, index.row());
auto* dc = new PatternDeltaCommand(p, ch, index.row() - 1);
auto& row = dc->row;//p->rowAt(ch, index.row());
auto* sm = static_cast<PatternEditorView*>(parent())->selectionModel();
@ -205,7 +206,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
auto mps = std::min(mpc, static_cast<size_t>(mp));
for (int r = s.y1; r <= s.y2; r++) {
if (multi && mps <= p->rowAt(c, r).numParams()) continue;
auto* dc = new PatternDeltaCommand(p, c, r);
auto* dc = new PatternDeltaCommand(p, c, r-1);
for (size_t i = dc->row.numParams(); i < mps; i++) dc->row.addParam(' ');
if (!multi) dc->row.param(mps) = {' ', 0};
cc->compose(dc);
@ -221,7 +222,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
for (int c = s.ch1; c <= s.ch2; c++) {
for (int r = s.y1; r <= s.y2; r++) {
auto* dc = new PatternDeltaCommand(p, c, r);
auto* dc = new PatternDeltaCommand(p, c, r-1);
if (s.portSelected(c)) dc->row.port = -1;
if (s.noteSelected(c)) dc->row.note = -1;
for (int i = static_cast<int>(dc->row.numParams()) - 1; i >= 0; i--) {

View File

@ -9,6 +9,7 @@ using Xybrid::UI::PatternEditorView;
#include <QString>
#include <QFontMetrics>
#include <QTimer>
#include <QScrollBar>
namespace { // helper functions
int cellWidthBase = -1;
@ -63,11 +64,22 @@ QVariant PatternEditorHeaderProxyModel::headerData(int section, Qt::Orientation
PatternEditorModel::PatternEditorModel(QObject *parent)
:QAbstractTableModel(parent) {
hprox = new PatternEditorHeaderProxyModel(parent, this);
auto view = static_cast<PatternEditorView*>(parent);
int rowHeight = [] {
QFontMetrics fm = QFontMetrics(QFont());
return (fm.boundingRect("255").size() + QSize(8, 4)).height();
}();
endHeight = (view->viewport()->height() - rowHeight) / 2;
connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, [this, view, rowHeight] {
endHeight = (view->viewport()->height() - rowHeight) / 2;
emit this->layoutChanged();
});
}
int PatternEditorModel::rowCount(const QModelIndex & /*parent*/) const {
//if (pattern->channels.size() == 0) return 1;
return pattern->rows + 1;
return pattern->rows + 2;
}
int PatternEditorModel::columnCount(const QModelIndex & /*parent*/) const {
@ -77,10 +89,11 @@ int PatternEditorModel::columnCount(const QModelIndex & /*parent*/) const {
QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) {
if (index.row() >= pattern->rows || index.column() >= colsPerChannel * static_cast<int>(pattern->channels.size())) return QVariant();
if (index.row() == 0) return QVariant();
if (index.row() > pattern->rows || index.column() >= colsPerChannel * static_cast<int>(pattern->channels.size())) return QVariant();
int cc = index.column() % colsPerChannel;
int ch = (index.column() - cc) / colsPerChannel;
auto& row = pattern->rowAt(ch, index.row());
auto& row = pattern->rowAt(ch, index.row()-1);
if (cc == 0) { // port
if (row.port >= 0 && row.port < 256) return QString::fromStdString(byteStr(row.port));
if (row.port == -2) return QString("(G)");
@ -115,12 +128,12 @@ QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
QVariant PatternEditorModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole) {
if (orientation == Qt::Orientation::Horizontal) return QVariant(); // blank actual-header
if (section >= pattern->rows) return QVariant(); // blank end section
return QString::number(section);
if (section == 0 || section > pattern->rows) return QVariant(); // blank end section
return QString::number(section-1);
} else if (role == Qt::SizeHintRole) {
auto fm = QFontMetrics(QFont(/*"Iosevka Term Light", 9*/));
if (orientation == Qt::Orientation::Vertical) {
if (section >= pattern->rows) return QSize(-1, -1); // take no space if no hanging room
if (section == 0 || section > pattern->rows) return QSize(-1, endHeight); // fill ends to center
return fm.boundingRect("255").size() + QSize(8, 4); // this should fit 0-999
}
return QSize(0, fm.height() + 4);
@ -129,7 +142,7 @@ QVariant PatternEditorModel::headerData(int section, Qt::Orientation orientation
}
Qt::ItemFlags PatternEditorModel::flags(const QModelIndex &index) const {
if (index.row() >= pattern->rows || index.column() >= colsPerChannel * static_cast<int>(pattern->channels.size())) {
if (index.row() == 0 || index.row() >= pattern->rows + 1 || (pattern->channels.size() > 0 && index.column() >= colsPerChannel * static_cast<int>(pattern->channels.size()))) {
return QAbstractTableModel::flags(index) & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
return QAbstractTableModel::flags(index);
@ -150,19 +163,10 @@ QVariant PatternEditorModel::hdrData(int section, Qt::Orientation, int role) con
if (cellWidthBase <= 0) {
auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
headerHeight = fm.height()+4;
cellWidthBase = fm.width(QString("FF")) + fm.width(QString("C#2")) + cellPadding*4;
cellWidthParamTab = fm.width(QString("v")) + cellPadding;
cellWidthParam = cellWidthParamTab + fm.width(QString("FF")) + cellPadding;
}/*
auto& c = pattern->channels.at(static_cast<size_t>(section));
size_t maxParams = 0;
for (auto& r : c.rows) {
if (r.numParams() > maxParams) maxParams = r.numParams();
cellWidthBase = fm.horizontalAdvance(QString("FF")) + fm.horizontalAdvance(QString("C#2")) + cellPadding*4;
cellWidthParamTab = fm.horizontalAdvance(QString("v")) + cellPadding;
cellWidthParam = cellWidthParamTab + fm.horizontalAdvance(QString("FF")) + cellPadding;
}
int width = cellWidthBase;
width += static_cast<int>(maxParams) * cellWidthParam;
if (maxParams < paramSoftCap) width += cellWidthParamTab;
return QSize(width, headerHeight);*/
return QSize(0, headerHeight);
}
return QVariant();
@ -222,10 +226,12 @@ void PatternEditorModel::updateFold() {
view->setUpdatesEnabled(false);
int ifold = 1;
if (folded && pattern->fold > 1) ifold = pattern->fold;
int rows = this->rowCount();
int rows = this->rowCount()-2;
view->setRowHidden(0, false);
view->setRowHidden(rows+1, false);
for (int i = 0; i < rows; i++) {
view->setRowHidden(i, false); // dispel any "phantoms" we might end up having
view->setRowHidden(i, (i < pattern->rows && i % ifold != 0));
view->setRowHidden(i+1, false); // dispel any "phantoms" we might end up having
view->setRowHidden(i+1, (i < pattern->rows && i % ifold != 0));
}
view->setUpdatesEnabled(true);
}

View File

@ -20,6 +20,7 @@ namespace Xybrid::UI {
bool fitHeaderToName = false;
bool folded = true;
int endHeight = -1;
PatternEditorModel(QObject *parent);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;

View File

@ -71,7 +71,7 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
horizontalHeader()->setStretchLastSection(true);
verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
verticalHeader()->setMinimumSectionSize(0);
verticalHeader()->setStretchLastSection(true);
//verticalHeader()->setStretchLastSection(true);
verticalHeader()->setSectionsClickable(false);
setCornerButtonEnabled(false);
//verticalHeader()->setDefaultAlignment(Qt::AlignTop);
@ -96,6 +96,10 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
setModel(&*mdl);
hdr->setModel(&*mdl->hprox);
// smooth scroll
setVerticalScrollMode(ScrollMode::ScrollPerPixel);
setHorizontalScrollMode(ScrollMode::ScrollPerPixel);
{ /* set up hotkeys */ } {
// transpose notes
auto transpose = [this](int amt, int key = Qt::Key_Alt) {
@ -157,8 +161,9 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
mdl->toggleFold();
auto p = mdl->getPattern();
auto ind = currentIndex();
if (mdl->folded && p->fold > 1 && ind.row() % p->fold != 0) {
ind = ind.siblingAtRow(ind.row() - (ind.row() % p->fold));
int row = ind.row() - 1;
if (mdl->folded && p->fold > 1 && row % p->fold != 0) {
ind = ind.siblingAtRow(1 + row - (row % p->fold));
setCurrentIndex(ind);
if (selectedIndexes().count() <= 1) selectionModel()->select(ind, QItemSelectionModel::SelectionFlag::ClearAndSelect);
}
@ -271,7 +276,9 @@ void PatternEditorView::keyPressEvent(QKeyEvent* e) {
return;
}
}
QAbstractItemView::keyPressEvent(e);
auto prevInd = currentIndex();
QTableView::keyPressEvent(e);
if (auto ind = currentIndex(); ind != prevInd) scrollTo(ind, ScrollHint::PositionAtCenter); // cursor position changed, scroll to keep up
if (!e->isAutoRepeat()) {
if (Util::keyToNote(e->key()) >= 0 || (e->key() >= Qt::Key_0 && e->key() <= Qt::Key_9) || e->key() == Qt::Key_Space) { // note-related key
startPreview(Util::unshiftedKey(e->key()));
@ -284,13 +291,22 @@ void PatternEditorView::keyReleaseEvent(QKeyEvent* e) {
if (!e->isAutoRepeat()) stopPreview(Util::unshiftedKey(e->key()));
}
void PatternEditorView::mouseReleaseEvent(QMouseEvent* e) {
QTableView::mouseReleaseEvent(e);
auto sm = selectionModel();
if (sm->selectedIndexes().size() <= 1) {
auto ind = indexAt(e->localPos().toPoint());
if (ind.isValid() && model()->flags(ind) & Qt::ItemFlag::ItemIsEnabled) scrollTo(ind, ScrollHint::PositionAtCenter);
}
}
void PatternEditorView::startPreview(int key) {
auto ind = currentIndex();
int cc = ind.column() % PatternEditorModel::colsPerChannel;
int ch = (ind.column() - cc) / PatternEditorModel::colsPerChannel;
if (cc == 1) { // note column
stopPreview(key); // end current preview first, if applicable
auto& r = mdl->getPattern()->rowAt(ch, ind.row());
auto& r = mdl->getPattern()->rowAt(ch, ind.row()-1);
auto p = mdl->getPattern()->project->shared_from_this();
previewKey[key] = {r.port, audioEngine->preview(p, r.port, r.note)};
}
@ -309,6 +325,14 @@ void PatternEditorView::setPattern(const std::shared_ptr<Pattern>& pattern) {
//horizontalHeader()->isSectionHidden()
//columnCountChanged(0, mdl->columnCount());
rowCountChanged(0, mdl->rowCount());
auto cur = currentIndex();
auto ind = model()->index(std::clamp(cur.row(), 1, pattern->rows), std::clamp(cur.column(), 0, std::max(0, mdl->columnCount() - 2)));
while (isColumnHidden(ind.column())) ind = ind.siblingAtColumn(ind.column()-1);
while (isRowHidden(ind.row())) ind = ind.siblingAtRow(ind.row()-1);
setCurrentIndex(ind);
selectionModel()->select(ind, QItemSelectionModel::ClearAndSelect);
scrollTo(ind, ScrollHint::PositionAtCenter);
}
void PatternEditorView::updateGeometries() {

View File

@ -36,6 +36,8 @@ namespace Xybrid::UI {
void keyReleaseEvent(QKeyEvent*) override;
void keyboardSearch(const QString&) override {} // disable accidental search
void mouseReleaseEvent(QMouseEvent*) override;
void setPattern(const std::shared_ptr<Data::Pattern>& pattern);
void updateHeader(bool full = false);