Commit f50b2575 authored by Leander Schulten's avatar Leander Schulten
Browse files

UI/UX: ProgramPrototypeView/ChannelProgramEditor: Greatly improve UI and UX! Fix crashes.

parent 4fec9750
Pipeline #193496 passed with stage
in 5 minutes and 34 seconds
#include "channelprogrammeditor.h"
#include <cmath>
#include <QSGGeometryNode>
#include <QSGFlatColorMaterial>
#include <QSGGeometryNode>
#include <QSGVertexColorMaterial>
#include <cmath>
namespace GUI{
namespace GUI {
CurrentTimePointWrapper::CurrentTimePointWrapper(ChannelProgrammEditor *editor):editor(editor){}
CurrentTimePointWrapper::CurrentTimePointWrapper(ChannelProgrammEditor *editor) : editor(editor) {}
void CurrentTimePointWrapper::setValue(unsigned char value){
void CurrentTimePointWrapper::setValue(unsigned char value) {
auto cur = editor->getCurrentTimePoint();
if(cur){
if(cur->value!=value){
if (cur) {
if (cur->value != value) {
cur->value = value;
emit valueChanged();
editor->update();
......@@ -19,83 +19,50 @@ void CurrentTimePointWrapper::setValue(unsigned char value){
}
}
unsigned char CurrentTimePointWrapper::getValue()const{
unsigned char CurrentTimePointWrapper::getValue() const {
auto cur = editor->getCurrentTimePoint();
if(cur)
return cur->value;
if (cur) return cur->value;
return 0;
}
bool CurrentTimePointWrapper::hasCurrent(){
bool CurrentTimePointWrapper::hasCurrent() {
return editor->haveCurrentTimePoint();
}
void CurrentTimePointWrapper::setTime(double time){
if(editor->channelProgramm){
void CurrentTimePointWrapper::setTime(double time) {
if (editor->channelProgramm) {
auto cur = editor->currentTimePoint;
if(cur==editor->channelProgramm->timeline.cend())
return;
if (cur == editor->channelProgramm->timeline.cend()) return;
double min = 0.;
if(cur!=editor->channelProgramm->timeline.begin()){
if (cur != editor->channelProgramm->timeline.begin()) {
--cur;
min = cur->time;
++cur;
}
++cur;
double max = 99999.;
if(cur!=editor->channelProgramm->timeline.cend()){
max=cur->time;
if (cur != editor->channelProgramm->timeline.cend()) {
max = cur->time;
}
time = std::max(min,std::min(max,time));
time = std::max(min, std::min(max, time));
--cur;
if(cur->time != time){
editor->channelProgramm->changeTimeOfTimePoint(cur->time,time);
if (cur->time != time) {
editor->channelProgramm->changeTimeOfTimePoint(cur->time, time);
editor->currentTimePoint = editor->channelProgramm->timeline.cend();
emit hasCurrentChanged();
emit timeChanged();
editor->update();
}
/*auto cur = editor->getCurrentTimePoint();
if(cur){
if(cur->value!=value){
cur->value = value;
emit valueChanged();
editor->update();
}
}*/
}
}
double CurrentTimePointWrapper::getTime()const{
double CurrentTimePointWrapper::getTime() const {
auto cur = editor->getCurrentTimePoint();
if(cur)
return cur->time;
if (cur) return cur->time;
return 0;
}
void CurrentTimePointWrapper::setCurve(const QEasingCurve::Type &c){
auto cur = editor->getCurrentTimePoint();
if(cur){
if(cur->easingCurveToNextPoint!=c){
cur->easingCurveToNextPoint.setType(c);
emit curveChanged();
editor->update();
}
}
}
QEasingCurve::Type CurrentTimePointWrapper::getCurve()const{
auto cur = editor->getCurrentTimePoint();
if(cur)
return cur->easingCurveToNextPoint.type();
return QEasingCurve::Type::Linear;
}
ChannelProgrammEditor::ChannelProgrammEditor():currentTimePointWrapper(this),modifier(0)
{
setFlag(ItemHasContents,true);
setFlag(ItemIsFocusScope,true);
//setFlag(ItemAcceptsInputMethod,true);
//setFlag(ItemAcceptsDrops,true);
ChannelProgrammEditor::ChannelProgrammEditor() : currentTimePointWrapper(this), modifier(0) {
setFlag(ItemHasContents, true);
setFlag(ItemIsFocusScope, true);
setAcceptedMouseButtons(Qt::AllButtons);
setAcceptHoverEvents(true);
setEnabled(true);
......@@ -105,66 +72,17 @@ ChannelProgrammEditor::ChannelProgrammEditor():currentTimePointWrapper(this),mod
setAntialiasing(true);
}
void ChannelProgrammEditor::setBackgroundItem(QQuickItem *b){
if(backgroundItem!=b){
if(backgroundItem){
backgroundItem->setParentItem(nullptr);
}
backgroundItem=b;
if(backgroundItem){
backgroundItem->setX(0);
backgroundItem->setY(0);
backgroundItem->setZ(-1);
backgroundItem->setWidth(width());
backgroundItem->setHeight(height());
backgroundItem->setVisible(true);
backgroundItem->setParentItem(this);
}
emit backgroundItemChanged();
}
}
void ChannelProgrammEditor::setValueChangeItem(QQuickItem *b){
if (b!=valueChangeItem) {
if(valueChangeItem){
b->setParentItem(nullptr);
}
valueChangeItem=b;
if(valueChangeItem){
valueChangeItem->setVisible(false);
valueChangeItem->setParentItem(this);
}
emit valueChangeItemChanged();
}
}
void ChannelProgrammEditor::setChannelProgramm(DMX::ChannelProgramm *p){
if(p!=channelProgramm){
channelProgramm=p;
void ChannelProgrammEditor::setChannelProgramm(DMX::ChannelProgramm *p) {
if (p != channelProgramm) {
channelProgramm = p;
currentTimePoint = channelProgramm->timeline.cend();
currentSegment = channelProgramm->timeline.cend();
update();
emit channelProgrammChanged();
}
}
void ChannelProgrammEditor::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry){
Q_UNUSED(newGeometry)
Q_UNUSED(oldGeometry)
updatePolish();
}
void ChannelProgrammEditor::updatePolish(){
//qDebug()<<"updatePolish\n";
if (backgroundItem) {
backgroundItem->setX(0);
backgroundItem->setY(0);
backgroundItem->setWidth(width());
backgroundItem->setHeight(height());
}
}
QSGNode * ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * nodeData){
QSGNode *ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *nodeData) {
Q_UNUSED(nodeData)
ChannelProgrammEditor::updatePolish();
QSGTransformNode *node = nullptr;
......@@ -174,9 +92,8 @@ QSGNode * ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNo
QSGGeometry *pointGeometry = nullptr;
const int segments = 128;
// effective version: const int count = channelProgramm?channelProgramm->timeline.empty()?0:channelProgramm->timeline.crbegin()->time*16:0;
const int count = channelProgramm?channelProgramm->timeline.empty()?0:channelProgramm->timeline.begin()->time<0.01?(channelProgramm->timeline.size()-1)*segments:channelProgramm->timeline.size()*segments:0;
const int pointCount = channelProgramm?channelProgramm->timeline.empty()?0:channelProgramm->timeline.size():0;
const int count = channelProgramm ? channelProgramm->timeline.empty() ? 0 : channelProgramm->timeline.begin()->time < 0.01 ? (channelProgramm->timeline.size() - 1) * segments : channelProgramm->timeline.size() * segments : 0;
const int pointCount = channelProgramm ? channelProgramm->timeline.empty() ? 0 : channelProgramm->timeline.size() : 0;
if (!oldNode) {
node = new QSGTransformNode;
......@@ -187,14 +104,12 @@ QSGNode * ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNo
lineNode->setGeometry(lineGeometry);
lineNode->setFlag(QSGNode::OwnsGeometry);
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
material->setColor(graphColor);
lineNode->setMaterial(material);
lineNode->setFlag(QSGNode::OwnsMaterial);
node->appendChildNode(lineNode);
pointNode = new QSGGeometryNode;
pointGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), pointCount);
pointGeometry->setLineWidth(5);
......@@ -211,36 +126,24 @@ QSGNode * ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNo
lineNode = static_cast<QSGGeometryNode *>(node->childAtIndex(0));
lineGeometry = lineNode->geometry();
lineGeometry->allocate(count);
static_cast<QSGFlatColorMaterial*>(lineNode->material())->setColor(graphColor);
static_cast<QSGFlatColorMaterial *>(lineNode->material())->setColor(graphColor);
pointNode = static_cast<QSGGeometryNode *>(node->childAtIndex(1));
pointGeometry = pointNode->geometry();
pointGeometry->allocate(pointCount);
}
QMatrix4x4 matrix;
if (modifier.testFlag(X_PRESSED)) {
xScale+=xDiff*1/32.;
}else{
totalXOffset += xDiff;
if(totalXOffset>0) // nach links beschränken
totalXOffset=0;
if(channelProgramm){ // nach rechts beschränken
if(!channelProgramm->timeline.empty()){
if (channelProgramm->timeline.crbegin()->time*xScale<-totalXOffset) {
totalXOffset=-channelProgramm->timeline.crbegin()->time*xScale;
}
}
}}
matrix.translate(totalXOffset,0);
matrix.scale(xScale,1.0);
matrix.translate(totalXOffset, 0);
matrix.scale(xScale, 1.0);
node->setMatrix(matrix);
if(count){
if (count) {
QRectF bounds = boundingRect();
QSGGeometry::Point2D *vertices = lineGeometry->vertexDataAsPoint2D();
//const qreal stepSize = 1./segments;
// const qreal stepSize = 1./segments;
auto iter = channelProgramm->timeline.cbegin();
/*
* effective version :
......@@ -261,31 +164,31 @@ QSGNode * ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNo
for(;iter!=channelProgramm->timeline.cend()&&i<count;++i) {
}*/
if (iter->time>0.01) {
if (iter->time > 0.01) {
const auto from = channelProgramm->timeline.crbegin()->value;
const auto to = iter->value;
for(int i = 0;i<segments;++i,++vertices){
const auto progress = i / (segments-1.);
for (int i = 0; i < segments; ++i, ++vertices) {
const auto progress = i / (segments - 1.);
vertices->x = progress * iter->time;
vertices->y = 255 - (channelProgramm->timeline.crbegin()->easingCurveToNextPoint.valueForProgress(progress) * (to-from)+from);
vertices->y = 255 - (channelProgramm->timeline.crbegin()->easingCurveToNextPoint.valueForProgress(progress) * (to - from) + from);
vertices->y /= 255.;
vertices->y *= bounds.height();
}
}
for(;;){
for (;;) {
const auto from = iter->value;
const auto offsetTime = iter->time;
const auto &curve = iter->easingCurveToNextPoint;
++iter;
if(iter==channelProgramm->timeline.cend()){
if (iter == channelProgramm->timeline.cend()) {
break;
}
const auto to = iter->value;
const auto timeDiff = iter->time - offsetTime;
for(int i = 0;i<segments;++i,++vertices){
const auto progress = i / (segments-1.);
for (int i = 0; i < segments; ++i, ++vertices) {
const auto progress = i / (segments - 1.);
vertices->x = progress * timeDiff + offsetTime;
vertices->y = 255 - (curve.valueForProgress(progress) * (to-from)+from);
vertices->y = 255 - (curve.valueForProgress(progress) * (to - from) + from);
vertices->y /= 255.;
vertices->y *= bounds.height();
}
......@@ -294,44 +197,42 @@ QSGNode * ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNo
QRectF bounds = boundingRect();
QSGGeometry::ColoredPoint2D *vertices = pointGeometry->vertexDataAsColoredPoint2D();
for(auto iter = channelProgramm->timeline.cbegin();iter!=channelProgramm->timeline.cend();++iter){
for (auto iter = channelProgramm->timeline.cbegin(); iter != channelProgramm->timeline.cend(); ++iter) {
vertices->x = iter->time;
vertices->y = 255 - iter->value;
vertices->y /= 255.;
vertices->y *= bounds.height();
if(iter == currentTimePoint){
vertices->r=0;
vertices->g=255;
vertices->b=0;
vertices->a=255;
}else{
if(lastMousePosition!=INVALID_POS){
const auto xDiff = (vertices->x-mapFromVisualX(lastMousePosition.x()))*(xScale);
const auto yDiff = vertices->y-lastMousePosition.y();
auto disstance = (int)std::sqrt(xDiff*xDiff+yDiff*yDiff)*2;
if(disstance<255){
vertices->r=255-disstance;
vertices->g=0;
vertices->b=disstance;
vertices->a=255;
}else{
vertices->r=0;
vertices->g=0;
vertices->b=255;
vertices->a=255;
}
}else{
vertices->r=0;
vertices->g=0;
vertices->b=255;
vertices->a=255;
if (iter == currentTimePoint) {
vertices->r = 0;
vertices->g = 255;
vertices->b = 0;
vertices->a = 255;
} else {
if (lastMousePosition != INVALID_POS) {
const auto xDiff = (vertices->x - mapFromVisualX(lastMousePosition.x())) * (xScale);
const auto yDiff = vertices->y - lastMousePosition.y();
auto disstance = (int)std::sqrt(xDiff * xDiff + yDiff * yDiff) * 2;
if (disstance < 255) {
vertices->r = 255 - disstance;
vertices->g = 0;
vertices->b = disstance;
vertices->a = 255;
} else {
vertices->r = 0;
vertices->g = 0;
vertices->b = 255;
vertices->a = 255;
}
} else {
vertices->r = 0;
vertices->g = 0;
vertices->b = 255;
vertices->a = 255;
}
}
++vertices;
}
}
}
lineNode->markDirty(QSGNode::DirtyGeometry);
......@@ -340,7 +241,64 @@ QSGNode * ChannelProgrammEditor::updatePaintNode(QSGNode *oldNode, UpdatePaintNo
return node;
}
decltype(DMX::ChannelProgramm::timeline)::iterator ChannelProgrammEditor::getTimePointForPosition(int x_, int y_){
void ChannelProgrammEditor::updateHoverSegment() {
auto i = channelProgramm->timeline.upper_bound(mapFromVisualX(lastMousePosition.x()));
double newX;
double newLength;
if (i == channelProgramm->timeline.end()) {
newX = 0;
newLength = 0;
} else {
// i is the first element
if (i == channelProgramm->timeline.begin()) {
newX = mapToVisualX(0);
newLength = xScale * i->time;
} else {
auto previous = i;
--previous;
newX = mapToVisualX(previous->time);
newLength = xScale * (i->time - previous->time);
}
}
if (newX != hoverSegmentX) {
hoverSegmentX = newX;
emit hoverSegmentXChanged();
}
if (newLength != hoverSegmentLength) {
hoverSegmentLength = newLength;
emit hoverSegmentLengthChanged();
}
}
void ChannelProgrammEditor::updateSelectedSegment() {
double newX;
double newLength;
if (currentSegment == channelProgramm->timeline.end()) {
newX = 0;
newLength = 0;
} else {
auto next = currentSegment;
++next;
// i is the first segment
if (next == channelProgramm->timeline.end()) {
newX = mapToVisualX(0);
newLength = xScale * channelProgramm->timeline.begin()->time;
} else {
newX = mapToVisualX(currentSegment->time);
newLength = xScale * (next->time - currentSegment->time);
}
}
if (newX != selectedSegmentX) {
selectedSegmentX = newX;
emit selectedSegmentXChanged();
}
if (newLength != selectedSegmentLength) {
selectedSegmentLength = newLength;
emit selectedSegmentLengthChanged();
}
}
decltype(DMX::ChannelProgramm::timeline)::iterator ChannelProgrammEditor::getTimePointForPosition(int x_, int y_) {
auto x = mapFromVisualX(x_);
auto y = getScaledY(y_);
if (channelProgramm->timeline.empty()) {
......@@ -348,20 +306,17 @@ decltype(DMX::ChannelProgramm::timeline)::iterator ChannelProgrammEditor::getTim
}
auto best = channelProgramm->timeline.end();
auto minDist = clickRadius;
// auto begin = channelProgramm->timeline.lower_bound(x-clickRadius);
// auto end = channelProgramm->timeline.upper_bound(x+clickRadius);
auto begin = channelProgramm->timeline.cbegin();
auto end = channelProgramm->timeline.cend();
if(begin==channelProgramm->timeline.cend()){
if (begin == channelProgramm->timeline.cend()) {
begin = channelProgramm->timeline.end();
--begin;
}
for(auto i = begin; i != end;++i){
const auto xDiff = (i->time-x)*(xScale);
const auto yDiff = i->value-y;
auto dist = std::sqrt(xDiff*xDiff+yDiff*yDiff);
qDebug()<<"dist"<<dist<<'\n'<<'\n';
if(dist < minDist){
for (auto i = begin; i != end; ++i) {
const auto xDiff = (i->time - x) * (xScale);
const auto yDiff = i->value - y;
auto dist = std::sqrt(xDiff * xDiff + yDiff * yDiff);
if (dist < minDist) {
minDist = dist;
best = i;
}
......@@ -369,253 +324,251 @@ decltype(DMX::ChannelProgramm::timeline)::iterator ChannelProgrammEditor::getTim
return best;
}
QEasingCurve * ChannelProgrammEditor::getCurveForPoint(int x){
if(!channelProgramm)
return nullptr;
if(channelProgramm->timeline.empty())
return nullptr;
QEasingCurve *ChannelProgrammEditor::getCurveForPoint(int x) {
if (!channelProgramm) return nullptr;
if (channelProgramm->timeline.empty()) return nullptr;
auto lowerBound = channelProgramm->timeline.lower_bound(x);
if(lowerBound==channelProgramm->timeline.cend()){
if (lowerBound == channelProgramm->timeline.cend()) {
return &channelProgramm->timeline.crbegin()->easingCurveToNextPoint;
}else{
} else {
return &lowerBound->easingCurveToNextPoint;
}
}
void ChannelProgrammEditor::setSelectedEasingCurve(QEasingCurve::Type type) {
if (currentSegment != channelProgramm->timeline.end() && currentSegment->easingCurveToNextPoint.type() != type) {
currentSegment->easingCurveToNextPoint.setType(type);
emit selectedEasingCurveChanged();
update();
}
}
void ChannelProgrammEditor::mousePressEvent(QMouseEvent *event){
void ChannelProgrammEditor::mousePressEvent(QMouseEvent *event) {
update();
lastMousePosition = event->pos();
forceActiveFocus(Qt::MouseFocusReason);
event->accept();
mousePressTimestamp = event->timestamp();
if(!channelProgramm)
return;
if((modifier.testFlag(T_PRESSED)||modifier.testFlag(V_PRESSED))&&valueChangeItem){
valueChangeItem->setVisible(true);
valueChangeItem->setX(event->x());
valueChangeItem->setY(event->y());
}
if(channelProgramm->timeline.empty()){
if(modifier.testFlag(N_PRESSED)){
qDebug()<<"Add time point<\n";
currentTimePoint = channelProgramm->timeline.insert(DMX::TimePoint(mapFromVisualX(event->x()),getScaledY(event->y()),QEasingCurve::Linear)).first;
if (!channelProgramm) return;
if (channelProgramm->timeline.empty()) {
if (modifier.testFlag(N_PRESSED)) {
currentTimePoint = channelProgramm->timeline.insert(DMX::TimePoint(mapFromVisualX(event->x()), getScaledY(event->y()), QEasingCurve::Linear)).first;
update();
updateHoverSegment();
}
return;
}
if(modifier.testFlag(D_PRESSED)){
qDebug()<<"D is pressed\n";
auto iter = getTimePointForPosition(event->x(),event->y());
if(iter != channelProgramm->timeline.end()){
if (modifier.testFlag(D_PRESSED)) {
auto iter = getTimePointForPosition(event->x(), event->y());
if (iter != channelProgramm->timeline.end()) {
if (iter == currentSegment) {
currentSegment = channelProgramm->timeline.end();
emit selectedEasingCurveChanged();
updateSelectedSegment();
}
channelProgramm->timeline.erase(iter);
update();
updateHoverSegment();
}
currentTimePoint = channelProgramm->timeline.cend();
emit currentTimePointWrapper.hasCurrentChanged();
}else{
qDebug()<<" N PRESSED : "<<modifier.testFlag(N_PRESSED)<<'\n';
if(modifier.testFlag(N_PRESSED)){
qDebug()<<"Add time point?\n";
if(getTimePointForPosition(event->x(),event->y())==channelProgramm->timeline.end()){
currentTimePoint = channelProgramm->timeline.insert(DMX::TimePoint(mapFromVisualX(event->x()),getScaledY(event->y()),QEasingCurve::Linear)).first;
} else {
if (modifier.testFlag(N_PRESSED)) {
if (getTimePointForPosition(event->x(), event->y()) == channelProgramm->timeline.end()) {
currentTimePoint = channelProgramm->timeline.insert(DMX::TimePoint(mapFromVisualX(event->x()), getScaledY(event->y()), QEasingCurve::Linear)).first;
update();
updateHoverSegment();
}
} else {
auto oldIter = currentTimePoint;
currentTimePoint = getTimePointForPosition(event->x(), event->y());
if (currentTimePoint == channelProgramm->timeline.end()) {
// no timepoints gets selected, lets select a segment
auto i = channelProgramm->timeline.upper_bound(mapFromVisualX(event->localPos().x()));
// the timepoint for the segment before the first timepoint is the last timepoint
if (i == channelProgramm->timeline.begin()) {
i = channelProgramm->timeline.end();
// we want the left timepoint of an segment
--i;
} else if (i != channelProgramm->timeline.end()) {
// we want the left timepoint of an segment
--i;
}
if (i != currentSegment) {
currentSegment = i;
emit selectedEasingCurveChanged();
updateSelectedSegment();
}
} else {
if (currentSegment != channelProgramm->timeline.end()) {
currentSegment = channelProgramm->timeline.end();
emit selectedEasingCurveChanged();
updateSelectedSegment();
}
}
}else{
currentTimePoint = getTimePointForPosition(event->x(),event->y());
qDebug()<<"currentTimePoint Changed";