diff --git a/source/qml/lib/Demo.qml b/source/qml/lib/Demo.qml index 416d03f..3762909 100644 --- a/source/qml/lib/Demo.qml +++ b/source/qml/lib/Demo.qml @@ -1,119 +1,120 @@ /* * Copyright 2018 Fabian Riethmayer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Controls 2.2 import org.kde.kirigami 2.4 as Kirigami import "tools.js" as T import "annotate.js" as A Rectangle { width: 320 height: 280 color: "white" Item { anchors.fill: parent id: root Row { spacing: Kirigami.Units.largeSpacing * 2 x: Kirigami.Units.gridUnit * 2 y: Kirigami.Units.gridUnit * 2 Rectangle { id: left1 width: Kirigami.Units.largeSpacing * 8 height: Kirigami.Units.largeSpacing * 8 color: "#27ae60" } Rectangle { id: right1 width: Kirigami.Units.largeSpacing * 8 height: Kirigami.Units.largeSpacing * 8 color: "#27ae60" anchors.top: parent.top; } } Row { spacing: Kirigami.Units.smallSpacing * 8 x: Kirigami.Units.gridUnit * 2 y: Kirigami.Units.gridUnit * 8 Rectangle { id: left2 width: Kirigami.Units.largeSpacing * 8 height: Kirigami.Units.largeSpacing * 8 color: "#27ae60" } Rectangle { id: right2 width: Kirigami.Units.largeSpacing * 12 height: Kirigami.Units.largeSpacing * 12 color: "#27ae60" anchors.top: parent.top; Rectangle { id: right3 width: Kirigami.Units.largeSpacing * 4 height: Kirigami.Units.largeSpacing * 4 color: "black" anchors.top: parent.top; anchors.right: parent.right; anchors.margins: Kirigami.Units.smallSpacing } } } } // HACK coordinates are only final after a small delay Timer { interval: 1000 repeat: false running: true onTriggered: { var a = new A.An(root); a.tree(); a.find("qquickrectangle").first().draw({ outline: {}, ruler: {}, brace: {to: new A.An(right2), text: "16px", center: false} }); a.find("qquickrectangle").eq(2).draw({ outline: {aspectratio: true}, + ruler: {horizontal: true} }); a.find("qquickrectangle").eq(3).draw({ messure: { to: a.find("qquickrectangle").eq(4)} }); } } // Draw helpers and anotation Raster { base: Kirigami.Units.gridUnit mobile: true desktop: true } } diff --git a/source/qml/lib/Outline.qml b/source/qml/lib/Outline.qml index 3f8b1cc..78923bb 100644 --- a/source/qml/lib/Outline.qml +++ b/source/qml/lib/Outline.qml @@ -1,119 +1,120 @@ /* * Copyright 2018 Fabian Riethmayer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Controls 2.2 +import org.kde.kirigami 2.4 as Kirigami import "tools.js" as T // Draw a frame around an element Item { anchors.fill: parent; property string color: "rgba(236, 161, 169, 0.6)" property bool label: true property bool aspectratio: false property Item item property Item root : container.parent z: 10 id: container Rectangle { id: prot color: "#cc93cee9" width: childrenRect.width + 10 height: childrenRect.height + 10 visible: false z: 2 Label { x: Kirigami.Units.smallSpacing y: Kirigami.Units.smallSpacing id: dim color: "#000" font.pointSize: 8 lineHeight: 8 height: 8 } } Canvas { anchors.fill: parent; id: canvas onPaint: { // get scale because annotation should not be scaled var scale = T.getScale(container.parent) var ctx = getContext("2d"); ctx.strokeStyle = container.color; ctx.lineWidth = 4 / scale ctx.beginPath(); // Draw an rectangle around the element var offset = ctx.lineWidth / 2; var cItem = item.mapToItem(container.root, 0, 0); ctx.moveTo(cItem.x + offset, cItem.y + offset); ctx.lineTo(cItem.x + offset, cItem.y + item.height - offset); ctx.lineTo(cItem.x + item.width - offset, cItem.y + item.height - offset); ctx.lineTo(cItem.x + item.width - offset, cItem.y + offset); ctx.lineTo(cItem.x + offset, cItem.y + offset); if (container.aspectratio) { // Write proportions of an object instead of dimensions / px ctx.moveTo(cItem.x + offset, cItem.y + offset); ctx.lineTo(cItem.x + item.width - offset, cItem.y + item.height - offset); ctx.moveTo(cItem.x + item.width - offset, cItem.y + offset); ctx.lineTo(cItem.x + offset, cItem.y + item.height - offset); var aspect = Math.round(item.width / item.height * 100) / 100 // Well known aspect ratios switch (aspect) { case 1: aspect = "1 x 1" break; case 1.33: aspect = "4 x 3" break; case 1.5: aspect = "3 x 2" break; case 1.78: aspect = "16 x 9" break; } dim.text = aspect prot.visible = true; prot.x = cItem.x + item.width / 2 - prot.width / 2 prot.y = cItem.y + item.height / 2 - prot.height / 2 } else { // Write dimensions / px if (container.label) { dim.text = Math.round(item.width * 100) / 100 + " x " + Math.round(item.height * 100) / 100; prot.visible = true; prot.x = cItem.x + item.width / 2 - prot.width / 2 prot.y = cItem.y + item.height / 2 - prot.height / 2 } } ctx.stroke(); } } } diff --git a/source/qml/lib/Ruler.qml b/source/qml/lib/Ruler.qml index 13cc2fa..ba4f470 100644 --- a/source/qml/lib/Ruler.qml +++ b/source/qml/lib/Ruler.qml @@ -1,66 +1,65 @@ /* * Copyright 2018 Fabian Riethmayer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Controls 2.2 import org.kde.kirigami 2.4 as Kirigami import "tools.js" as T // Draw a ruler for highlighting alignment Item { id: canvas anchors.fill: parent; - property int rx; - property int ry; + property int offset; property bool horizontal: true; property string stroke: "rgba(41,128,185, 1)" property double scale: T.getScale(canvas) // using Rectangles because they scale smooth Row { - y: canvas.ry + y: canvas.offset x: 0 id: hackRow visible: canvas.horizontal spacing: Kirigami.Units.smallSpacing / scale Repeater { model: new Array(Math.floor(canvas.width / hackRow.spacing / 2)) Rectangle { width: Kirigami.Units.smallSpacing / scale height: 2 / scale color: "#2980b9" } } } Column { - x: canvas.rx + x: canvas.offset y: 0 id: hackColumn visible: !canvas.horizontal spacing: Kirigami.Units.smallSpacing / scale Repeater { model: new Array(Math.floor(canvas.height / hackColumn.spacing / 2)) Rectangle { height: Kirigami.Units.smallSpacing / scale width: 1 / scale color: "#2980b9" } } } } diff --git a/source/qml/lib/annotate.js b/source/qml/lib/annotate.js index 7f83770..4719c7c 100644 --- a/source/qml/lib/annotate.js +++ b/source/qml/lib/annotate.js @@ -1,222 +1,234 @@ var ruler = Qt.createComponent("Ruler.qml"); var brace = Qt.createComponent("Brace.qml"); var outline = Qt.createComponent("Outline.qml"); var messure = Qt.createComponent("Messure.qml"); var padding = Qt.createComponent("Padding.qml"); // get classname and strip _QML of the name function getClassName(obj) { var str = obj.toString(); str = str.substring(0, str.indexOf("(")); if (str.search(/_QML/) !== -1) { str = str.substring(0, str.indexOf("_QML")); } return str.toLowerCase(); } +// Merge 2 objects of options +function getOpts(opts, choices) { + for (var choice in choices) { + opts[choice] = choices[choice]; + } + return opts; +} + // An extended array of QML elements function An(node) { this.nodes = []; if (typeof node === "undefined") { this.nodes = [] } else if (typeof node === "Array") { this.nodes = node; } else { this.nodes = [node]; } } // Find an An of QML elements from this point down the subtrees An.prototype.find = function(selector) { var result = new An(); if (typeof selector === "string") { selector = new Select(selector); } /* for debugging for (var member in this) { if (typeof this[member] !== "function") { console.log(member + ": " + this[member]); } }*/ // iterate threw the children // apply the selector and traverse down the tree for (var n = 0; n < this.nodes.length; n++) { var node = this.nodes[n]; for (var i = 0; i < node.children.length; i++) { if (selector.match(node.children[i])) { // Add matching element to result result.nodes.push(node.children[i]); } if (node.children[i].children.length) { // Merge matching results of subrtree var child = new An(node.children[i]); result.concat(child.find(selector)); } } } return result; } // Search only direct children An.prototype.children = function(selector) { var result = new An(); if (typeof selector === "string") { selector = new Select(selector); } for (var n = 0; n < this.nodes.length; n++) { var node = this.nodes[n]; for (var i = 0; i < node.children.length; i++) { if (selector.match(node.children[i])) { // Add matching element to result result.nodes.push(node.children[i]); } } } return result; } An.prototype.concat = function(n) { this.nodes = this.nodes.concat(n.nodes); } An.prototype.first = function() { if (this.nodes.length > 0) { return new An(this.nodes[0]); } return new An(); } An.prototype.last = function() { if (this.nodes.length > 0) { return new An(this.nodes[this.nodes.length - 1]); } return new An(); } An.prototype.eq = function(n) { if (this.nodes.length > n) { return new An(this.nodes[n]); } return new An(); } /** * Draw a tree of all the elements */ An.prototype.tree = function(lvl) { if (typeof lvl === "undefined") { lvl = "" } /* for debug for (var member in this.nodes) { if (typeof this[member] !== "function") { console.log("|" + lvl + " " + member + ": " + (typeof this[member])); } }*/ for (var n = 0; n < this.nodes.length; n++) { var node = this.nodes[n]; console.log("|" + lvl + " " + getClassName(node) + " " + node.toString()); for (var i = 0; i < node.children.length; i++) { var child = new An(node.children[i]); child.tree(lvl + "--"); } } } /** * Drawing annotation on the nodes */ An.prototype.draw = function(obj) { console.log(this.nodes) for (var n = 0; n < this.nodes.length; n++) { var node = this.nodes[n]; var opt; for (var type in obj) { if (Array.isArray(obj[type])) { for (var i = 0; i < obj[type].length; i++) { this._draw(node, type, obj[type][i]); } } else { this._draw(node, type, obj[type]); } } } return this; } /** * Internal method to draw */ An.prototype._draw = function(node, type, opt) { //console.log("drawing " + type) switch (type) { case "outline": outline.createObject(root, {item: node, label: opt.label, aspectratio: opt.aspectratio}); break case "ruler": - ruler.createObject(root, {rx: node.mapToItem(null, 0, 0).x, horizontal: false}); + var options = getOpts({ + offset: opt.horizontal ? node.mapToItem(null, 0, 0).y : node.mapToItem(null, 0, 0).x, + horizontal: false + }, opt); + ruler.createObject(root, options); break case "padding": padding.createObject(root, {item: node}); break case "brace": brace.createObject(root, {"from": node, "to": opt.to.nodes[0], "text": opt.text, "center": opt.center, "horizontal": opt.horizontal}); break case "messure": messure.createObject(root, {"from": node, "to": opt.to.nodes[0], "type": opt.type}); break } } /** * Selector for qml elements */ function Select(str) { // TODO support more complex syntax // - multiple nodenames, hirachy, ... if (str.search(/\{/) !== -1) { this.nodeName = str.substring(0, str.indexOf("{")); var members = str.match(/\{.+\}/); try { this.attrs = JSON.parse(members[0]); } catch(e) { console.log("Could not parse attributes"); console.log(e); } } else { this.nodeName = str; } } /** * Check if the node matches the selector */ Select.prototype.match = function(node) { if (this.nodeName === "*" || getClassName(node) === this.nodeName) { if (typeof this.attrs !== "undefined") { // TODO only return true if all attributes match for (var attr in this.attrs) { if (typeof node[attr] !== "undefined" && node[attr].toString() === this.attrs[attr]) { return true; } } } else { return true; } } return false; }