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
This diff is collapsed.
#ifndef CHANNELPROGRAMMEDITOR_H #ifndef CHANNELPROGRAMMEDITOR_H
#define CHANNELPROGRAMMEDITOR_H #define CHANNELPROGRAMMEDITOR_H
#include <QQuickItem>
#include <QFlags> #include <QFlags>
#include <QQuickItem>
#include <dmx/programmprototype.h> #include <dmx/programmprototype.h>
namespace GUI{ namespace GUI {
class ChannelProgrammEditor; class ChannelProgrammEditor;
class CurrentTimePointWrapper : public QObject{ class CurrentTimePointWrapper : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(unsigned char value READ getValue WRITE setValue NOTIFY valueChanged) Q_PROPERTY(unsigned char value READ getValue WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(bool hasCurrent READ hasCurrent NOTIFY hasCurrentChanged) Q_PROPERTY(bool hasCurrent READ hasCurrent NOTIFY hasCurrentChanged)
Q_PROPERTY(double time READ getTime WRITE setTime NOTIFY timeChanged) Q_PROPERTY(double time READ getTime WRITE setTime NOTIFY timeChanged)
Q_PROPERTY(QEasingCurve::Type curveToNext READ getCurve WRITE setCurve NOTIFY curveChanged) ChannelProgrammEditor *editor;
ChannelProgrammEditor * editor;
public: public:
CurrentTimePointWrapper(ChannelProgrammEditor * editor); CurrentTimePointWrapper(ChannelProgrammEditor *editor);
void setValue(unsigned char value); void setValue(unsigned char value);
unsigned char getValue()const; unsigned char getValue() const;
bool hasCurrent(); bool hasCurrent();
void setTime(double time); void setTime(double time);
double getTime()const; double getTime() const;
void setCurve(const QEasingCurve::Type &c);
QEasingCurve::Type getCurve()const;
signals: signals:
void valueChanged(); void valueChanged();
void hasCurrentChanged(); void hasCurrentChanged();
void timeChanged(); void timeChanged();
void curveChanged();
}; };
class ChannelProgrammEditor : public QQuickItem class ChannelProgrammEditor : public QQuickItem {
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(CurrentTimePointWrapper * currentTimePoint READ getCurrentTimePointWrapper CONSTANT) Q_PROPERTY(CurrentTimePointWrapper *currentTimePoint READ getCurrentTimePointWrapper CONSTANT)
Q_PROPERTY(QQuickItem* background READ getBackgroundItem WRITE setBackgroundItem NOTIFY backgroundItemChanged)
Q_PROPERTY(QQuickItem* informationDisplayTextItem READ getValueChangeItem WRITE setValueChangeItem NOTIFY valueChangeItemChanged)
Q_PROPERTY(int currentChangingValue READ getCurrentChangingValue NOTIFY currentChangingValueChanged)
Q_PROPERTY(int clickRadius MEMBER clickRadius NOTIFY clickRadiusChanged) Q_PROPERTY(int clickRadius MEMBER clickRadius NOTIFY clickRadiusChanged)
Q_PROPERTY(QColor graphColor MEMBER graphColor NOTIFY graphColorChanged) Q_PROPERTY(QColor graphColor MEMBER graphColor NOTIFY graphColorChanged)
Q_PROPERTY(QString tooltipText READ getTooltipText CONSTANT) Q_PROPERTY(DMX::ChannelProgramm *channelProgramm READ getChannelProgramm WRITE setChannelProgramm NOTIFY channelProgrammChanged)
Q_PROPERTY(DMX::ChannelProgramm* channelProgramm READ getChannelProgramm WRITE setChannelProgramm NOTIFY channelProgrammChanged) Q_PROPERTY(double hoverSegmentX READ getHoverSegmentX NOTIFY hoverSegmentXChanged)
Q_PROPERTY(double hoverSegmentLength READ getHoverSegmentLength NOTIFY hoverSegmentLengthChanged)
Q_PROPERTY(double selectedSegmentX READ getSelectedSegmentX NOTIFY selectedSegmentXChanged)
Q_PROPERTY(double selectedSegmentLength READ getSelectedSegmentLength NOTIFY selectedSegmentLengthChanged)
Q_PROPERTY(QEasingCurve::Type selectedEasingCurve READ getSelectedEasingCurve WRITE setSelectedEasingCurve NOTIFY selectedEasingCurveChanged)
Q_PROPERTY(double mouseX READ getMouseX NOTIFY mouseXChanged)
private: private:
friend class CurrentTimePointWrapper; friend class CurrentTimePointWrapper;
CurrentTimePointWrapper currentTimePointWrapper; CurrentTimePointWrapper currentTimePointWrapper;
DMX::ChannelProgramm * channelProgramm = nullptr; DMX::ChannelProgramm *channelProgramm = nullptr;
QQuickItem * backgroundItem = nullptr;
QQuickItem * valueChangeItem = nullptr;
decltype(DMX::ChannelProgramm::timeline)::iterator currentTimePoint; decltype(DMX::ChannelProgramm::timeline)::iterator currentTimePoint;
int clickRadius=4; decltype(DMX::ChannelProgramm::timeline)::iterator currentSegment;
double xScale = 4.; int clickRadius = 4;
double xScale = 50.;
double totalXOffset = 0; double totalXOffset = 0;
double xDiff = 0 ; QColor graphColor = QColor(0, 0, 0);
QColor graphColor = QColor(0,0,0); enum Modifier { X_PRESSED = 0x1, Y_PRESSED = 0x2, N_PRESSED = 0x4, D_PRESSED = 0x20 };
enum Modifier{X_PRESSED=0x1,Y_PRESSED=0x2,N_PRESSED=0x4,T_PRESSED=0x8,V_PRESSED=0x10,D_PRESSED=0x20};
QFlags<Modifier> modifier; QFlags<Modifier> modifier;
#define INVALID_POS QPoint(std::numeric_limits<int>::max(),std::numeric_limits<int>::max())
QPoint lastMousePosition = INVALID_POS; double hoverSegmentX = 0;
double hoverSegmentLength = 0;
void updateHoverSegment();
double selectedSegmentX = 0;
double selectedSegmentLength = 0;
void updateSelectedSegment();
static constexpr auto INVALID_POS = QPointF(std::numeric_limits<qreal>::lowest(), std::numeric_limits<qreal>::lowest());
QPointF lastMousePosition = INVALID_POS;
ulong mousePressTimestamp; ulong mousePressTimestamp;
decltype(DMX::ChannelProgramm::timeline)::iterator getTimePointForPosition(int x, int y); decltype(DMX::ChannelProgramm::timeline)::iterator getTimePointForPosition(int x, int y);
/** /**
...@@ -70,7 +74,7 @@ private: ...@@ -70,7 +74,7 @@ private:
* @param x the x coordinate * @param x the x coordinate
* @return x * xScale + totalXOffset * @return x * xScale + totalXOffset
*/ */
double mapToVisualX(double x){return x * xScale + totalXOffset;} double mapToVisualX(double x) const { return x * xScale + totalXOffset; }
/** /**
* @brief mapFromVisualX maps from the visual Position of the graph to real values * @brief mapFromVisualX maps from the visual Position of the graph to real values
* @param xthe x coordinate * @param xthe x coordinate
...@@ -80,44 +84,50 @@ private: ...@@ -80,44 +84,50 @@ private:
* result = (x-totalXOffset)*(1/xScale) = 100 * result = (x-totalXOffset)*(1/xScale) = 100
* @return * @return
*/ */
double mapFromVisualX(double x){return (x-totalXOffset) * (1/xScale);} double mapFromVisualX(double x) const { return (x - totalXOffset) * (1 / xScale); }
int getScaledY(int y){return 255 - int(y / height() * 255);} int getScaledY(int y) const { return 255 - int(y / height() * 255); }
public: public:
ChannelProgrammEditor(); ChannelProgrammEditor();
void setBackgroundItem(QQuickItem*b); void setChannelProgramm(DMX::ChannelProgramm *p);
QQuickItem* getBackgroundItem()const{return backgroundItem;} DMX::ChannelProgramm *getChannelProgramm() const { return channelProgramm; }
void setValueChangeItem(QQuickItem*b); bool haveCurrentTimePoint() const { return channelProgramm ? (currentTimePoint != channelProgramm->timeline.cend()) : false; }
QQuickItem* getValueChangeItem()const{return valueChangeItem;} DMX::TimePoint *getCurrentTimePoint();
int getCurrentChangingValue()const{return haveCurrentTimePoint()?currentTimePoint->value:0;} CurrentTimePointWrapper *getCurrentTimePointWrapper() { return &currentTimePointWrapper; }
void setChannelProgramm(DMX::ChannelProgramm*p); Q_INVOKABLE QEasingCurve *getCurveForPoint(int x);
DMX::ChannelProgramm* getChannelProgramm()const{return channelProgramm;} double getHoverSegmentX() const { return hoverSegmentX; }
QString getTooltipText()const; double getHoverSegmentLength() const { return hoverSegmentLength; }
bool haveCurrentTimePoint()const{return channelProgramm?(currentTimePoint!=channelProgramm->timeline.cend()):false;} double getSelectedSegmentX() const { return selectedSegmentX; }
DMX::TimePoint * getCurrentTimePoint(); double getSelectedSegmentLength() const { return selectedSegmentLength; }
CurrentTimePointWrapper * getCurrentTimePointWrapper(){return &currentTimePointWrapper;} QEasingCurve::Type getSelectedEasingCurve() const { return channelProgramm && currentSegment != channelProgramm->timeline.end() ? currentSegment->easingCurveToNextPoint.type() : static_cast<QEasingCurve::Type>(-1); }
Q_INVOKABLE QEasingCurve * getCurveForPoint(int x); void setSelectedEasingCurve(QEasingCurve::Type type);
double getMouseX() const { return lastMousePosition.x(); }
Q_INVOKABLE double getTimeForVisualX(double x) const { return mapFromVisualX(x); }
Q_INVOKABLE int getValueForVisualX(double x) const { return channelProgramm ? channelProgramm->getValueForTime(std::max(0., mapFromVisualX(x))) : -1; }
protected: protected:
virtual void updatePolish()override; virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)override; virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual QSGNode* updatePaintNode(QSGNode *, UpdatePaintNodeData *)override; virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event)override; virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void mousePressEvent(QMouseEvent *event)override; virtual void hoverEnterEvent(QHoverEvent *e) override;
virtual void mouseReleaseEvent(QMouseEvent *event)override; virtual void hoverMoveEvent(QHoverEvent *e) override;
virtual void hoverEnterEvent(QHoverEvent*e)override{lastMousePosition=e->pos();e->accept();forceActiveFocus(Qt::MouseFocusReason);} virtual void hoverLeaveEvent(QHoverEvent *e) override;
virtual void hoverMoveEvent(QHoverEvent*e)override; virtual void keyPressEvent(QKeyEvent *event) override;
virtual void hoverLeaveEvent(QHoverEvent*e)override{lastMousePosition=INVALID_POS;update();e->accept();} virtual void keyReleaseEvent(QKeyEvent *event) override;
virtual void keyPressEvent(QKeyEvent *event)override; virtual void wheelEvent(QWheelEvent *event) override;
virtual void keyReleaseEvent(QKeyEvent *event)override;
virtual void wheelEvent(QWheelEvent *event)override;
signals: signals:
void backgroundItemChanged();
void valueChangeItemChanged();
void currentChangingValueChanged(int);
void clickRadiusChanged(); void clickRadiusChanged();
void graphColorChanged(); void graphColorChanged();
void channelProgrammChanged(); void channelProgrammChanged();
void click(int x, int y); void click(int x, int y);
void rightClick(int x, int y); void rightClick(int x, int y);
void hoverSegmentXChanged();
void hoverSegmentLengthChanged();
void selectedSegmentXChanged();
void selectedSegmentLengthChanged();
void selectedEasingCurveChanged();
void mouseXChanged();
}; };
} // namespace GUI } // namespace GUI
......
...@@ -6,13 +6,14 @@ import custom.licht 1.0 ...@@ -6,13 +6,14 @@ import custom.licht 1.0
import "components" import "components"
ModelView{ ModelView{
model:programmPrototypeModel model: programmPrototypeModel
id:modelView id: modelView
addButton.text: "Add Prototype" addButton.text: "Add Prototype"
removeButton.text: "Remove Prototype" removeButton.text: "Remove Prototype"
onAddClicked: dialog.visible = true onAddClicked: dialog.visible = true
onRemoveClicked: ModelManager.removeDmxProgramPrototype(remove); onRemoveClicked: ModelManager.removeDmxProgramPrototype(remove);
rows: 5
sortModel: ListModel{ sortModel: ListModel{
ListElement{ ListElement{
name: "Creation Date" name: "Creation Date"
...@@ -27,136 +28,172 @@ ModelView{ ...@@ -27,136 +28,172 @@ ModelView{
sortPropertyName: "devicePrototype.name" sortPropertyName: "devicePrototype.name"
} }
} }
Label{
Layout.topMargin: 4
Layout.columnSpan: 2
text: "Die einzelnen ChannelProgramme :"
font.underline: true
}
ListView{ ListView{
clip:true clip: true
Layout.column: 2
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.row: 2
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
header: Text{ id: channelView
text:"Die einzelnen ChannelProgramme : " spacing: 32
color: Material.foreground model: modelView.currentModelData ? modelView.currentModelData.channelProgramms : null
height:70 ScrollBar.vertical: ScrollBar {
policy: channelView.contentHeight > channelView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
} }
id : channelView delegate: ColumnLayout{
spacing: 50 width: channelView.width
delegate:
ChannelProgrammEditor{
layer.samples: 4
clickRadius: 50
antialiasing: true
id:editor
width: parent.width
//onClicked: channelView.currentIndex = index
height:100
background: Rectangle{color:"lightgrey"}
channelProgramm: modelData
focus: true
informationDisplayTextItem:Text{}
RowLayout{ RowLayout{
height:50 Layout.preferredHeight: 50
anchors.bottom: editor.top Layout.fillWidth: true
spacing: 8
Label{ Label{
text:editor.channelProgramm.channel.name text: editor.channelProgramm.channel.name
font.bold: true font.bold: true
Layout.minimumWidth: 60 Layout.minimumWidth: 60
} }
Label{ Label{
text:"time: " text: "Time: "
visible: editor.currentTimePoint.hasCurrent visible: editor.currentTimePoint.hasCurrent
} }
TextInputField{ TextInputField{
Layout.minimumWidth: 30 Layout.minimumWidth: 30
text:editor.currentTimePoint.time text: editor.currentTimePoint.time
visible: editor.currentTimePoint.hasCurrent visible: editor.currentTimePoint.hasCurrent
onAccepted: editor.currentTimePoint.time = text onAccepted: editor.currentTimePoint.time = text
} }
Label{ Label{
text:"value: " text:"Value: "
visible: editor.currentTimePoint.hasCurrent visible: editor.currentTimePoint.hasCurrent
} }
TextInputField{ TextInputField{
Layout.minimumWidth: 30 Layout.minimumWidth: 30
text:editor.currentTimePoint.value text: editor.currentTimePoint.value
visible: editor.currentTimePoint.hasCurrent visible: editor.currentTimePoint.hasCurrent
onAccepted: editor.currentTimePoint.value = text onAccepted: editor.currentTimePoint.value = text
validator: IntValidator{bottom: 0; top:255} validator: IntValidator{bottom: 0; top:255}
} }
Label{ Label{
text: "To Next:" text: "Curve type:"
visible: editor.currentTimePoint.hasCurrent visible: editor.selectedEasingCurve >= 0
} }
ComboBox{ ComboBox{
model: easingModel model: easingModel
property bool open: false property bool open: false
currentIndex: editor.currentTimePoint.curveToNext Layout.preferredWidth: 150
visible: editor.currentTimePoint.hasCurrent currentIndex: editor.selectedEasingCurve
visible: editor.selectedEasingCurve >= 0
onDownChanged: { onDownChanged: {
open|=down; open|=down;
} }
onHighlighted: { onHighlighted: {
if(open)editor.currentTimePoint.curveToNext = index if(open)editor.selectedEasingCurve = index
} }
onActivated: { onActivated: {
editor.currentTimePoint.curveToNext = index editor.selectedEasingCurve = index
open=false; open=false;
} }
} }
} // RowLayout
ChannelProgrammEditor{
id: editor
Layout.preferredHeight: 100
Layout.fillWidth: true
clickRadius: 36
channelProgramm: modelData
Rectangle{
anchors.fill: parent
color: "lightgrey"
z: -1
}
Rectangle{
x: editor.hoverSegmentX
z: -0.5
width: editor.hoverSegmentLength
height: 100
color: Qt.rgba(.7,.7,.7)
}
Rectangle{
x: editor.selectedSegmentX
z: -0.4
width: editor.selectedSegmentLength
height: 100
color: Qt.rgba(.5,.5,.5)
}
Rectangle{
anchors.top: parent.bottom
visible: editor.mouseX >= 0
x: editor.mouseX
width: 1
height: 4
color: Material.foreground
Label{
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: editor.mouseX < contentWidth/2 ? -(editor.mouseX - contentWidth/2) : 0
horizontalAlignment: Text.AlignHCenter
text: editor.getValueForVisualX(editor.mouseX) + " at\n" + editor.getTimeForVisualX(editor.mouseX).toFixed(2);
} }
onRightClick:{
console.log(x + " "+y)
}
} }
} // ChannelProgrammEditor
} // delegate: ColumnLayout
} // ListView
model: modelView.currentModelData ? modelView.currentModelData.channelProgramms : null Label{
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.margins: 5
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
text: "Hold N and click to create a new TimePoint. You can select a TimePoint to change its values. A selected TimePoint can be deleted with d. You can drag a TimePoint. Hold x while scrolling to change the zooming."
} }
Popup{ Popup{
modal: true modal: true
id:dialog id: dialog
width:300 width: 300
x: (parent.width - width) / 2 x: (parent.width - width) / 2
y: (parent.height - height) / 2 y: (parent.height - height) / 2
contentItem: ColumnLayout{ contentItem: ColumnLayout{
spacing: 10 spacing: 10
ComboBox{ ComboBox{
Layout.fillWidth: true Layout.fillWidth: true
id:prototype id: prototype
model: devicePrototypeModel model: devicePrototypeModel
textRole: "display" textRole: "display"
} }
RowLayout{ RowLayout{
Label{ Label{
id:nameLabel id: nameLabel
text:"Name :" text: "Name :"
} }
TextInputField{ TextInputField{
Layout.fillWidth: true Layout.fillWidth: true
id:name id: name
} }
} }
RowLayout{ RowLayout{
Label{ Label{
text:"Description :" text: "Description :"
} }
TextInputField{ TextInputField{
Layout.fillWidth: true Layout.fillWidth: true
id:description id: description
} }
} }
RowLayout{ RowLayout{
Button{ Button{
Layout.fillWidth: true Layout.fillWidth: true
text:"Abbrechen" text: "Abbrechen"
onClicked: dialog.visible = false onClicked: dialog.visible = false
} }
Button{ Button{
Layout.fillWidth: true Layout.fillWidth: true
text:"Erzeugen" text: "Erzeugen"
onClicked: { onClicked: {
if(name.text===""){ if(name.text===""){
name.underlineColor = "red"; name.underlineColor = "red";
...@@ -165,9 +202,8 @@ ModelView{ ...@@ -165,9 +202,8 @@ ModelView{
ModelManager.addProgrammPrototype(prototype.currentIndex,name.text,description.text); ModelManager.addProgrammPrototype(prototype.currentIndex,name.text,description.text);
} }
} }
} } // Button
} } // RowLayout
} } // contentItem: ColumnLayout
} } // Popup
} }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment