pattern strut, multiselect performance fix (only one UI update, thanks!)
parent
d107547e1b
commit
12a563afc4
31
notes
31
notes
|
@ -45,33 +45,18 @@ project data {
|
|||
|
||||
TODO {
|
||||
immediate frontburner {
|
||||
- implement saving (yay, QCbor*)
|
||||
- save empty space as a number of skipped rows (make sure to have something keeping a row count!)
|
||||
|
||||
undo {
|
||||
- single-row pattern edits
|
||||
- multi-select edits
|
||||
|
||||
- channel move
|
||||
- channel rename
|
||||
- channel add
|
||||
- channel delete
|
||||
|
||||
- pattern move
|
||||
- pattern rename
|
||||
- pattern add (w/ duplication and sequence insert, as single index)
|
||||
- pattern delete (w/ resulting sequence edit)
|
||||
|
||||
- generic sequencer edit
|
||||
|
||||
make selection follow pattern move where applicable
|
||||
group 1 {
|
||||
- make selection follow pattern move where applicable
|
||||
- strut command in pattern editor (mostly selection agnostic)
|
||||
}
|
||||
group 2 {
|
||||
skeleton audio engine
|
||||
skeleton plugin registry
|
||||
skeleton graph and node
|
||||
}
|
||||
|
||||
# fix how qt5.12 broke header text (removed elide for now)
|
||||
|
||||
- multiselect editing (at least delete) [ delete done ]
|
||||
pattern editing - spacebar for strut (param placeholder)
|
||||
|
||||
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include "editing/projectcommands.h"
|
||||
#include "editing/patterncommands.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
using Xybrid::Data::Project;
|
||||
using Xybrid::Data::Pattern;
|
||||
using namespace Xybrid::Editing;
|
||||
|
@ -15,25 +17,35 @@ CompositeCommand::CompositeCommand(std::shared_ptr<Project> p) {
|
|||
project = p;
|
||||
}
|
||||
|
||||
CompositeCommand* CompositeCommand::reserve(int count) {
|
||||
if (count <= 0) return this;
|
||||
children.reserve(static_cast<size_t>(count));
|
||||
return this;
|
||||
}
|
||||
|
||||
CompositeCommand* CompositeCommand::compose(ProjectCommand* c) {
|
||||
if (!project) {
|
||||
project = c->project;
|
||||
} else if (c->project != project) return this; // fail
|
||||
if (!children.empty() && children.back()->mergeWith(c)) delete c;
|
||||
} else if (c->project != project && !c->cancel()) return this; // fail
|
||||
c->composed = true;
|
||||
if (!children.empty() && children.back()->mergeWith(c)) c->cancel();
|
||||
else children.emplace_back(c);
|
||||
return this;
|
||||
}
|
||||
CompositeCommand* CompositeCommand::compose(PatternCommand* c) {
|
||||
if (!project) {
|
||||
project = c->pattern->project->socket->window->getProject();
|
||||
} else if (c->pattern->project != project.get()) return this; // fail
|
||||
if (!children.empty() && children.back()->mergeWith(c)) delete c;
|
||||
} else if (c->pattern->project != project.get() && !c->cancel()) return this; // fail
|
||||
if (!pattern) pattern = c->pattern;
|
||||
c->composed = true;
|
||||
if (!children.empty() && children.back()->mergeWith(c)) c->cancel();
|
||||
else children.emplace_back(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
bool CompositeCommand::commit(QString name) {
|
||||
if (!project->socket || !project->socket->undoStack) return cancel();
|
||||
if (!project || !project->socket || !project->socket->undoStack) return cancel();
|
||||
if (children.empty()) return cancel(); // no children? no action
|
||||
if (!name.isEmpty()) setText(name);
|
||||
project->socket->undoStack->push(this);
|
||||
return true;
|
||||
|
@ -45,10 +57,12 @@ bool CompositeCommand::cancel() {
|
|||
|
||||
void CompositeCommand::redo() {
|
||||
for (auto i = children.begin(); i != children.end(); i++) (*i)->redo();
|
||||
if (pattern) emit project->socket->patternUpdated(pattern.get());
|
||||
}
|
||||
|
||||
void CompositeCommand::undo() {
|
||||
for (auto i = children.rbegin(); i != children.rend(); i++) (*i)->undo();
|
||||
if (pattern) emit project->socket->patternUpdated(pattern.get());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Xybrid::Editing {
|
|||
class PatternCommand;
|
||||
class CompositeCommand : public QUndoCommand {
|
||||
std::shared_ptr<Data::Project> project;
|
||||
std::shared_ptr<Data::Pattern> pattern;
|
||||
std::vector<std::unique_ptr<QUndoCommand>> children;
|
||||
|
||||
public:
|
||||
|
@ -22,6 +23,8 @@ namespace Xybrid::Editing {
|
|||
CompositeCommand(std::shared_ptr<Data::Project>);
|
||||
~CompositeCommand() override = default;
|
||||
|
||||
CompositeCommand* reserve(int);
|
||||
|
||||
CompositeCommand* compose(ProjectCommand*);
|
||||
CompositeCommand* compose(PatternCommand*);
|
||||
|
||||
|
|
|
@ -41,12 +41,12 @@ bool PatternDeltaCommand::mergeWith(const QUndoCommand* o_) {
|
|||
|
||||
void PatternDeltaCommand::redo() {
|
||||
pattern->rowAt(ch, rw) = row;
|
||||
emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
|
||||
if (!composed) emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
|
||||
}
|
||||
|
||||
void PatternDeltaCommand::undo() {
|
||||
pattern->rowAt(ch, rw) = oldRow;
|
||||
emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
|
||||
if (!composed) emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
|
||||
}
|
||||
|
||||
PatternRenameCommand::PatternRenameCommand(const std::shared_ptr<Pattern>& pattern, const std::string &to) {
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Xybrid::Editing {
|
|||
friend class CompositeCommand;
|
||||
protected:
|
||||
std::shared_ptr<Data::Pattern> pattern;
|
||||
bool composed = false;
|
||||
|
||||
public:
|
||||
bool commit();
|
||||
|
|
|
@ -66,19 +66,23 @@ bool ProjectPatternMoveCommand::mergeWith(const QUndoCommand* o_) {
|
|||
}
|
||||
|
||||
void ProjectPatternMoveCommand::redo() {
|
||||
bool move = project->socket->window->patternSelection() == from && project->socket->window->sequenceSelection() == -1;
|
||||
auto p = project->patterns[static_cast<size_t>(from)];
|
||||
project->patterns.erase(project->patterns.begin() + from);
|
||||
project->patterns.insert(project->patterns.begin() + to, p);
|
||||
project->updatePatternIndices();
|
||||
emit project->socket->updatePatternLists();
|
||||
if (move) project->socket->window->patternSelection(to);
|
||||
}
|
||||
|
||||
void ProjectPatternMoveCommand::undo() {
|
||||
bool move = project->socket->window->patternSelection() == to && project->socket->window->sequenceSelection() == -1;
|
||||
auto p = project->patterns[static_cast<size_t>(to)];
|
||||
project->patterns.erase(project->patterns.begin() + to);
|
||||
project->patterns.insert(project->patterns.begin() + from, p);
|
||||
project->updatePatternIndices();
|
||||
emit project->socket->updatePatternLists();
|
||||
if (move) project->socket->window->patternSelection(from);
|
||||
}
|
||||
|
||||
ProjectPatternAddCommand::ProjectPatternAddCommand(const std::shared_ptr<Project>& project, int at, int atSeq, const std::shared_ptr<Pattern>& copyOf) {
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Xybrid::Editing {
|
|||
friend class CompositeCommand;
|
||||
protected:
|
||||
std::shared_ptr<Data::Project> project;
|
||||
bool composed = false;
|
||||
|
||||
public:
|
||||
bool commit();
|
||||
|
|
|
@ -270,14 +270,14 @@ void MainWindow::onNewProjectLoaded() {
|
|||
undoStack->clear();
|
||||
|
||||
project->socket = socket.get();
|
||||
Pattern* pt = project->patterns.front().get();
|
||||
for (auto* p : project->sequence) { // find first non-spacer in sequence, else fall back to first pattern numerically
|
||||
if (p == nullptr) continue;
|
||||
pt = p;
|
||||
updatePatternLists();
|
||||
patternSelection(0);
|
||||
sequenceSelection(-1);
|
||||
for (size_t i = 0; i < project->sequence.size(); i++) { // find first non-spacer in sequence, else fall back to first pattern numerically
|
||||
if (project->sequence[i] == nullptr) continue;
|
||||
sequenceSelection(static_cast<int>(i));
|
||||
break;
|
||||
}
|
||||
updatePatternLists();
|
||||
selectPatternForEditing(pt);
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace {
|
|||
|
||||
struct SelectionBounds {
|
||||
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
|
||||
int ch1, ch2;
|
||||
SelectionBounds(const QModelIndexList& sel) {
|
||||
x1 = std::numeric_limits<int>::max();
|
||||
y1 = std::numeric_limits<int>::max();
|
||||
|
@ -49,6 +50,8 @@ namespace {
|
|||
x2 = std::max(x2, s.column());
|
||||
y2 = std::max(y2, s.row());
|
||||
}
|
||||
ch1 = (x1 - (x1 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
|
||||
ch2 = (x2 - (x2 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool portSelected(int c) {
|
||||
|
@ -63,6 +66,9 @@ namespace {
|
|||
int cx = (c * PatternEditorModel::colsPerChannel) + 2 + (p*2);
|
||||
return (cx+1 >= x1 && cx <= x2);
|
||||
}
|
||||
[[maybe_unused]] int maxParamSelected(int c) {
|
||||
return std::min((x2 - (c * PatternEditorModel::colsPerChannel) - 2) / 2, PatternEditorModel::paramSoftCap);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -97,7 +103,7 @@ void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewI
|
|||
align = Qt::AlignVCenter | Qt::AlignLeft;
|
||||
painter->setPen(ColorScheme::current.patternFgBlank);
|
||||
} else {
|
||||
if (s == QString(" - ")) painter->setPen(ColorScheme::current.patternFgBlank);
|
||||
if (s == QString(" - ") || s == QString("- ")) painter->setPen(ColorScheme::current.patternFgBlank);
|
||||
else if (cc == 0) painter->setPen(ColorScheme::current.patternFgPort);
|
||||
else if (cc == 1) painter->setPen(ColorScheme::current.patternFgNote);
|
||||
else if (cc % 2 == 0) painter->setPen(ColorScheme::current.patternFgParamCmd);
|
||||
|
@ -134,22 +140,43 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
|
|||
|
||||
auto* sm = static_cast<PatternEditorView*>(parent())->selectionModel();
|
||||
auto sel = sm->selectedIndexes();
|
||||
bool multi = sel.size() > 1;
|
||||
|
||||
if (mod & Qt::Modifier::CTRL) {
|
||||
|
||||
} else if (mod & Qt::Modifier::ALT) {
|
||||
|
||||
} else {
|
||||
if (sel.size() > 1) {
|
||||
if (k == Qt::Key_Space) {
|
||||
if (mod & Qt::Modifier::SHIFT) return dc->cancel(); // TODO: once playback is a thing, shift+space to preview row?
|
||||
|
||||
dc->cancel();
|
||||
SelectionBounds s(sel);
|
||||
auto* cc = (new CompositeCommand())->reserve((1 + s.y2 - s.y1) * (1 + s.ch2 - s.ch1));
|
||||
|
||||
for (int c = s.ch1; c <= s.ch2; c++) {
|
||||
size_t mpc = 0; // max params in channel
|
||||
for (int r = 0; r < p->rows; r++) mpc = std::max(mpc, p->rowAt(c, r).numParams());
|
||||
auto mp = s.maxParamSelected(c);
|
||||
if (mp < 0) continue;
|
||||
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);
|
||||
for (size_t i = dc->row.numParams(); i < mps; i++) dc->row.addParam(' ');
|
||||
if (!multi) dc->row.param(mps) = {' ', 0};
|
||||
cc->compose(dc);
|
||||
}
|
||||
}
|
||||
return cc->commit("pattern strut");
|
||||
}
|
||||
if (multi) {
|
||||
if (k == Qt::Key_Delete) {
|
||||
dc->cancel();
|
||||
auto* cc = new CompositeCommand();
|
||||
|
||||
SelectionBounds s(sel);
|
||||
int chMin = (s.x1 - (s.x1 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
|
||||
int chMax = (s.x2 - (s.x2 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
|
||||
auto* cc = (new CompositeCommand())->reserve((1 + s.y2 - s.y1) * (1 + s.ch2 - s.ch1));
|
||||
|
||||
for (int c = chMin; c <= chMax; c++) {
|
||||
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);
|
||||
if (s.portSelected(c)) dc->row.port = -1;
|
||||
|
@ -216,7 +243,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
|
|||
size_t par = static_cast<size_t>((cc - (cc % 2)) / 2 - 1);
|
||||
if (k == Qt::Key_Insert) { // insert from within any place in the param columns
|
||||
if (row.numParams() >= PatternEditorModel::paramSoftCap) return false; // no overruns
|
||||
row.insertParam(par);
|
||||
row.insertParam(par, ' ');
|
||||
auto view = static_cast<PatternEditorView*>(parent());
|
||||
size_t cpar = row.numParams() - 1;
|
||||
if (cpar > par) cpar = par;
|
||||
|
@ -251,7 +278,6 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
|
|||
if (k == Qt::Key_Delete || k == Qt::Key_Insert || k == Qt::Key_Backspace) return false;
|
||||
char chr = static_cast<QKeyEvent*>(event)->text().toUtf8()[0];
|
||||
row.addParam(chr);
|
||||
// update whole row (phew!)
|
||||
auto view = static_cast<PatternEditorView*>(parent());
|
||||
view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(row.numParams()) * 2 + 1));
|
||||
return dc->commit();
|
||||
|
|
|
@ -96,7 +96,10 @@ QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
|
|||
if (row.numParams() == cp) return QString("» ");
|
||||
return QString("");
|
||||
}
|
||||
if (row.numParams() > cp) return QString::fromStdString(byteStr(row.params->at(cp)[1]));
|
||||
if (row.numParams() > cp) {
|
||||
if (row.params->at(cp)[0] == ' ') return QString("- ");
|
||||
return QString::fromStdString(byteStr(row.params->at(cp)[1]));
|
||||
}
|
||||
return QString("");
|
||||
//return QString("--");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue