Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various #31

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 3 additions & 1 deletion data/qmltoolbox/qml/QmlToolbox/Base/StyleDefault.qml
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ Item

property real pipelineSlotSize: 20 // Diameter of slots
property color pipelineSlotColorIn: '#ffffff' // Color of input slots
property color pipelineSlotColorOut: '#cafd00' // Color of output slots
property color pipelineSlotColorOut: '#ffffff' // Color of output slots
property color pipelineSlotColorOutRequired: '#cafd00' // Color of required output slots

property color pipelineLineColorDefault: '#000000' // Color of connections
property color pipelineLineColorHighlighted: '#6688c8' // Color of connections when highlighted
property color pipelineLineColorSelected: '#c83366' // Color of connections when selected
property color pipelineLineColorFeedback: '#afafaf' // Color of feedback connections
}
40 changes: 26 additions & 14 deletions data/qmltoolbox/qml/QmlToolbox/PipelineEditor/Connectors.qml
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,24 @@ Item
var splitPath = path.split('.');
return splitPath[splitPath.length - 1];
};
var isEqual = function (path, slot, combinedPath) {
return path == getPath(combinedPath) && slot == getSlot(combinedPath);
}

// Get all stages of the pipeline and the pipeline itself
var stages = [];

stages.push(path);

var pipeline = properties.getStage(path);
var pipelineInfo = properties.getStage(path);

for (var i in pipeline.stages)
for (var i in pipelineInfo.stages)
{
stages.push(path + '.' + pipeline.stages[i]);
stages.push(path + '.' + pipelineInfo.stages[i]);
}

// Get connectors
var pipeline = connectors.pipeline;
for (var i in stages)
{
// Get stage
Expand All @@ -87,35 +91,42 @@ Item
{
// Get connection
var connection = connections[j];
var from = connection.from;
var to = connection.to;

var from = connection.from;
var to = connection.to;
var feedback = connection.feedback || false;

// Draw connection
var p0 = connectors.pipeline.getSlotPos(getPath(from), getSlot(from));
var p1 = connectors.pipeline.getSlotPos(getPath(to), getSlot(to));
var p0 = pipeline.getSlotPos(getPath(from), getSlot(from));
var p1 = pipeline.getSlotPos(getPath(to), getSlot(to));

if (p0 != null && p1 != null)
{
// Highlight the connection if its input or output slot is selected
var status = (connectors.pipeline.hoveredElement == from || connectors.pipeline.hoveredElement == to) ? 1 : 0;
var status = feedback ? 3 : 0;
if (isEqual(pipeline.hoveredPath, pipeline.hoveredSlot, from) ||
isEqual(pipeline.hoveredPath, pipeline.hoveredSlot, to))
{
status = 1;
}

drawConnector(ctx, p0, p1, status);
}
}
}

// Draw interactive connector
if (connectors.pipeline.selectedOutput != '')
if (pipeline.selectedOutput != '')
{
var p0 = connectors.pipeline.getSlotPos(connectors.pipeline.selectedPath, connectors.pipeline.selectedOutput);
var p1 = { x: connectors.pipeline.mouseX, y: connectors.pipeline.mouseY };
var p0 = pipeline.getSlotPos(pipeline.selectedPath, pipeline.selectedOutput);
var p1 = { x: pipeline.mouseX, y: pipeline.mouseY };
drawConnector(ctx, p0, p1, 2);
}

if (connectors.pipeline.selectedInput != '')
if (pipeline.selectedInput != '')
{
var p0 = { x: connectors.pipeline.mouseX, y: connectors.pipeline.mouseY };
var p1 = connectors.pipeline.getSlotPos(connectors.pipeline.selectedPath, connectors.pipeline.selectedInput);
var p0 = { x: pipeline.mouseX, y: pipeline.mouseY };
var p1 = pipeline.getSlotPos(pipeline.selectedPath, pipeline.selectedInput);
drawConnector(ctx, p0, p1, 2);
}
}
Expand All @@ -142,6 +153,7 @@ Item
var color = Ui.style.pipelineLineColorDefault;
if (status == 1) color = Ui.style.pipelineLineColorHighlighted;
if (status == 2) color = Ui.style.pipelineLineColorSelected;
if (status == 3) color = Ui.style.pipelineLineColorFeedback;

ctx.strokeStyle = color;
ctx.fillStyle = color;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Item
height: item.radius

radius: width / 2.0
color: item.selected ? Ui.style.pipelineLineColorSelected : (item.hovered ? Ui.style.pipelineLineColorHighlighted : item.color)
color: item.selected ? Ui.style.pipelineLineColorSelected : (item.hovered ? Ui.style.pipelineLineColorHighlighted : (status !== null && status.required ? Ui.style.pipelineSlotColorOutRequired : item.color))
border.color: item.borderColor
border.width: item.borderWidth

Expand Down
163 changes: 155 additions & 8 deletions data/qmltoolbox/qml/QmlToolbox/PipelineEditor/Pipeline.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Item
{
id: pipeline

signal stageCreated(string path) ///< Signals creation of a new stage item after the pipeline was loaded

// Options
property var properties: null ///< Interface for accessing the actual properties
property string path: '' ///< Path in the pipeline hierarchy (e.g., 'pipeline')
Expand Down Expand Up @@ -103,7 +105,7 @@ Item
{
return function()
{
pipeline.createStage(type, type);
pipeline.createStage(type, type, menu.x, menu.y);
};
};

Expand Down Expand Up @@ -200,11 +202,23 @@ Item
}

// Add pseudo stages for inputs and outputs of the pipeline itself
addInputStageItem (pipeline.path, 'Inputs', 20, 150);
addOutputStageItem(pipeline.path, 'Outputs', 1200, 150);
addInputStageItem (pipeline.path, 'Inputs', 20, 150);
addOutputStageItem(pipeline.path, 'Outputs', x, 150);

// Do the layout
computeLayout();
// Hack: layout after 5 ms to ensure loading of stages is finished
function Timer() {
return Qt.createQmlObject("import QtQuick 2.0; Timer {}", pipeline);
}
function delay(delayTime, cb) {
var timer = new Timer();
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
delay(10, function() {
computeLayout();
});

// Redraw connections
connectors.requestPaint();
Expand Down Expand Up @@ -360,13 +374,15 @@ Item
* @param[in] name
* Name of stage
*/
function createStage(className, name)
function createStage(className, name, x, y)
{
// Create stage
var realName = properties.createStage(pipeline.path, className, name);

// Create stage item
addStageItem(pipeline.path + '.' + realName, realName, 100, 100);
addStageItem(pipeline.path + '.' + realName, realName, x, y);

stageCreated(pipeline.path + '.' + realName);
}

/**
Expand All @@ -390,12 +406,143 @@ Item
connectors.requestPaint();
}

/**
* Compute ranks in half ordered graph
*/
function computeRanks()
{
var getPath = function (path) {
var splitPath = path.split('.');
splitPath.splice(splitPath.length - 1, 1);
return splitPath.join('.');
};

// build the empty graph
var graph = {};
var inverseGraph = {};
var pathToName = {};
for (var name in stageItems)
{
graph[stageItems[name].path] = [];
inverseGraph[stageItems[name].path] = [];
pathToName[stageItems[name].path] = name;
}
pathToName["root"] = "Outputs";

// insert edges
for (var name in stageItems)
{
var connections = properties.getConnections(stageItems[name].path);
for (var i in connections)
{
var fromPath = getPath(connections[i].from);
var toPath = getPath(connections[i].to);

if (fromPath == "root" || connections[i].feedback)
{
// ignore connections from root (inputs will be leftmost)
// and feedback connections
// this lets the algorithm start at the output node
continue;
}

if (graph[fromPath].indexOf(toPath) < 0)
{
graph[fromPath].push(toPath);
}

if (inverseGraph[toPath].indexOf(fromPath) < 0)
{
inverseGraph[toPath].push(fromPath);
}
}
}

var ranks = [];
var toRemove = [];
while (Object.keys(graph).length > 0)
{
for (var path in graph)
{
if (graph[path].length === 0)
{
toRemove.push(path);
}
}

if (toRemove.length === 0)
{
console.log("circular graph detected, abort layout process.");
break;
}

var names = [];
for (var i in toRemove)
{
names.push(pathToName[toRemove[i]]);

}
ranks.push(names);

for (var i in toRemove)
{
var current = toRemove[i];
for (var j in inverseGraph[current])
{
var predecessor = inverseGraph[current][j];
var edgeNum = graph[predecessor].indexOf(current);
graph[predecessor].splice(edgeNum, 1);
}
delete graph[current];
}
toRemove = [];
}
ranks.push(["Inputs"]);

return ranks;
}

/**
* Compute automatic layout for stages
*/
function computeLayout()
{
// [TODO]
var startX = 120;
var marginX = 100;
var startY = 120;
var marginY = 100;

var x = startX;

var ranks = computeRanks();

for (var i in ranks)
{
// go through the computed ranks in reverse order
var stages = ranks[ranks.length - i - 1];

var maxWidth = 0;
for (var j in stages)
{
var current = stageItems[stages[j]];

maxWidth = Math.max(maxWidth, current.width);
}

var y = startY;
for (var j in stages)
{
var current = stageItems[stages[j]];

current.x = x + (maxWidth - current.width) / 2;
current.y = y;

y += current.height + marginY;
}
x += maxWidth + marginX;
}

connectors.requestPaint();
}

/**
Expand Down
12 changes: 11 additions & 1 deletion data/qmltoolbox/qml/QmlToolbox/PipelineEditor/PipelineEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ Rectangle
id: panel

// Options
property var properties: null ///< Interface for communicating with the actual properties
property var properties: null ///< Interface for communicating with the actual properties
property var pipeline: pipeline ///< Interface for communication from root

// Internals
property bool loaded: false
Expand Down Expand Up @@ -61,4 +62,13 @@ Rectangle
loaded = true;
}
}

/**
* Update pipeline (reload on different model)
*/
function update()
{
pipeline.path = "";
pipeline.path = "root";
}
}
27 changes: 25 additions & 2 deletions data/qmltoolbox/qml/QmlToolbox/PipelineEditor/Stage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Item

MenuItem
{
text: 'Add Input'
text: 'Add Input...'

onTriggered:
{
Expand All @@ -69,7 +69,7 @@ Item

MenuItem
{
text: 'Add Output'
text: 'Add Output...'

onTriggered:
{
Expand All @@ -78,6 +78,16 @@ Item
dialog.open();
}
}

MenuItem
{
text: 'Delete Stage'

onTriggered:
{
stage.closed();
}
}
}

// Stage body
Expand Down Expand Up @@ -368,6 +378,19 @@ Item
return null;
}

/**
* Add custom component to stage body
*
* @param[in] component
* The component from which one instance should be added
* @param[in] options
* Options for component creation
*/
function addComponent(component, options)
{
return component.createObject(inputs, options);
}

/**
* Load pipeline
*/
Expand Down
Loading