Add transformations
by pressing Ctrl key rectangle and text: rotate circle: extend (ellipse) and rotate line: cubic Bezier curve
This commit is contained in:
parent
220bf0143c
commit
8718e219a2
143
draw.js
143
draw.js
|
|
@ -38,7 +38,7 @@ const Prefs = Extension.imports.prefs;
|
|||
const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext;
|
||||
|
||||
var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4 };
|
||||
var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Circle", 3: "Rectangle", 4: "Text" };
|
||||
var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text" };
|
||||
var LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' };
|
||||
var LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' };
|
||||
var FontWeightNames = { 0: 'Normal', 1: 'Bold' };
|
||||
|
|
@ -119,7 +119,9 @@ var DrawingArea = new Lang.Class({
|
|||
for (let i = 0; i < this.elements.length; i++) {
|
||||
this.elements[i].buildCairo(cr, false);
|
||||
|
||||
if (this.elements[i].fill && this.elements[i].shape != Shapes.LINE)
|
||||
let isStraightLine = this.elements[i].shape == Shapes.LINE &&
|
||||
(this.elements[i].points.length < 3 || this.elements[i].points[2] == this.elements[i].points[1] || this.elements[i].points[2] == this.elements[i].points[0]);
|
||||
if (this.elements[i].fill && !isStraightLine)
|
||||
cr.fill();
|
||||
else
|
||||
cr.stroke();
|
||||
|
|
@ -227,6 +229,7 @@ var DrawingArea = new Lang.Class({
|
|||
fill: fill,
|
||||
eraser: eraser,
|
||||
shape: this.currentShape == Shapes.TEXT ? Shapes.RECTANGLE : this.currentShape,
|
||||
transform: { active: false, center: [0, 0], angle: 0, startAngle: 0, ratio: 1 },
|
||||
text: '',
|
||||
font: { family: (this.currentFontFamilyId == 0 ? this.fontFamily : FontFamilyNames[this.currentFontFamilyId]), weight: this.currentFontWeight, style: this.currentFontStyle },
|
||||
points: [[startX, startY]]
|
||||
|
|
@ -237,7 +240,8 @@ var DrawingArea = new Lang.Class({
|
|||
let [s, x, y] = this.transform_stage_point(coords[0], coords[1]);
|
||||
if (!s)
|
||||
return;
|
||||
this._updateDrawing(x, y);
|
||||
let controlPressed = event.has_control_modifier();
|
||||
this._updateDrawing(x, y, controlPressed);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -268,11 +272,17 @@ var DrawingArea = new Lang.Class({
|
|||
this._redisplay();
|
||||
},
|
||||
|
||||
_updateDrawing: function(x, y) {
|
||||
_updateDrawing: function(x, y, controlPressed) {
|
||||
if (!this.currentElement)
|
||||
return;
|
||||
if (this.currentElement.shape == Shapes.NONE)
|
||||
this.currentElement.addPoint(x, y, this.smoothedStroke);
|
||||
else if (this.currentElement.shape == Shapes.RECTANGLE && (controlPressed || this.currentElement.transform.active))
|
||||
this.currentElement.transformRectangle(x, y);
|
||||
else if (this.currentElement.shape == Shapes.ELLIPSE && (controlPressed || this.currentElement.transform.active))
|
||||
this.currentElement.transformEllipse(x, y);
|
||||
else if (this.currentElement.shape == Shapes.LINE && (controlPressed || this.currentElement.transform.active))
|
||||
this.currentElement.transformLine(x, y);
|
||||
else
|
||||
this.currentElement.points[1] = [x, y];
|
||||
this._redisplay();
|
||||
|
|
@ -549,24 +559,37 @@ var DrawingElement = new Lang.Class({
|
|||
|
||||
Clutter.cairo_set_source_color(cr, this.color);
|
||||
|
||||
let [points, shape] = [this.points, this.shape];
|
||||
let [points, shape, trans] = [this.points, this.shape, this.transform];
|
||||
|
||||
if (shape == Shapes.NONE || shape == Shapes.LINE) {
|
||||
if (shape == Shapes.LINE && points.length == 3) {
|
||||
cr.moveTo(points[0][0], points[0][1]);
|
||||
cr.curveTo(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1]);
|
||||
} else if (shape == Shapes.NONE || shape == Shapes.LINE) {
|
||||
cr.moveTo(points[0][0], points[0][1]);
|
||||
for (let j = 1; j < points.length; j++) {
|
||||
cr.lineTo(points[j][0], points[j][1]);
|
||||
}
|
||||
|
||||
} else if (shape == Shapes.ELLIPSE && points.length == 2) {
|
||||
this.rotate(cr, trans.angle + trans.startAngle, trans.center[0], trans.center[1]);
|
||||
this.scale(cr, trans.ratio, trans.center[0], trans.center[1]);
|
||||
let r = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||
cr.arc(points[0][0], points[0][1], r, 0, 2 * Math.PI);
|
||||
this.scale(cr, 1 / trans.ratio, trans.center[0], trans.center[1]);
|
||||
this.rotate(cr, - (trans.angle + trans.startAngle), trans.center[0], trans.center[1]);
|
||||
|
||||
} else if (shape == Shapes.RECTANGLE && points.length == 2) {
|
||||
this.rotate(cr, trans.angle, trans.center[0], trans.center[1]);
|
||||
cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||
this.rotate(cr, - trans.angle, trans.center[0], trans.center[1]);
|
||||
|
||||
} else if (shape == Shapes.TEXT && points.length == 2) {
|
||||
this.rotate(cr, trans.angle, trans.center[0], trans.center[1]);
|
||||
cr.selectFontFace(this.font.family, this.font.style, this.font.weight);
|
||||
cr.setFontSize(Math.abs(points[1][1] - points[0][1]));
|
||||
cr.moveTo(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]));
|
||||
cr.showText((showTextCursor) ? (this.text + "_") : this.text);
|
||||
this.rotate(cr, - trans.angle, trans.center[0], trans.center[1]);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -574,9 +597,11 @@ var DrawingElement = new Lang.Class({
|
|||
let row = "\n ";
|
||||
let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]);
|
||||
let color = this.eraser ? bgColor : this.color.to_string();
|
||||
let attributes = `fill="${this.fill ? color : 'transparent'}" ` +
|
||||
`stroke="${this.fill ? 'transparent' : color}" ` +
|
||||
`${this.fill ? 'stroke-opacity="0"' : 'fill-opacity="0"'} ` +
|
||||
let isStraightLine = this.shape == Shapes.LINE && (points.length < 3 || points[2] == points[1] || points[2] == points[0]);
|
||||
let fill = this.fill && !isStraightLine;
|
||||
let attributes = `fill="${fill ? color : 'transparent'}" ` +
|
||||
`stroke="${fill ? 'transparent' : color}" ` +
|
||||
`${fill ? 'stroke-opacity="0"' : 'fill-opacity="0"'} ` +
|
||||
`stroke-width="${this.line.lineWidth}" ` +
|
||||
`stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}" ` +
|
||||
`stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`;
|
||||
|
|
@ -584,21 +609,43 @@ var DrawingElement = new Lang.Class({
|
|||
if (this.dash.array[0] > 0 && this.dash.array[1] > 0)
|
||||
attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`;
|
||||
|
||||
if (this.shape == Shapes.NONE || this.shape == Shapes.LINE) {
|
||||
if (this.shape == Shapes.LINE && points.length == 3) {
|
||||
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
|
||||
row += ` C ${points[0][0]} ${points[0][1]}, ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}`;
|
||||
row += `${fill ? 'z' : ''}"/>`;
|
||||
|
||||
} else if (this.shape == Shapes.NONE || this.shape == Shapes.LINE) {
|
||||
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
row += ` L ${points[i][0]} ${points[i][1]}`;
|
||||
}
|
||||
row += `${fill ? 'z' : ''}"/>`;
|
||||
|
||||
} else if (this.shape == Shapes.ELLIPSE && points.length == 2 && this.transform.ratio != 1) {
|
||||
let ry = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||
let rx = ry * this.transform.ratio;
|
||||
let angle = (this.transform.angle + this.transform.startAngle) * 180 / Math.PI;
|
||||
row += `<ellipse ${attributes} cx="${points[0][0]}" cy="${points[0][1]}" rx="${rx}" ry="${ry}" transform="rotate(${angle}, ${points[0][0]}, ${points[0][1]})"/>`;
|
||||
|
||||
row += `${this.fill ? 'z' : ''}"/>`;
|
||||
} else if (this.shape == Shapes.ELLIPSE && points.length == 2) {
|
||||
let r = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||
row += `<circle ${attributes} cx="${points[0][0]}" cy="${points[0][1]}" r="${r}"/>`;
|
||||
|
||||
} else if (this.shape == Shapes.RECTANGLE && points.length == 2) {
|
||||
let transAttribute = "";
|
||||
if (this.transform.angle != 0) {
|
||||
let angle = this.transform.angle * 180 / Math.PI;
|
||||
transAttribute = ` transform="rotate(${angle}, ${this.transform.center[0]}, ${this.transform.center[1]})"`;
|
||||
}
|
||||
row += `<rect ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.min(points[0][1], points[1][1])}" ` +
|
||||
`width="${Math.abs(points[1][0] - points[0][0])}" height="${Math.abs(points[1][1] - points[0][1])}"/>`;
|
||||
`width="${Math.abs(points[1][0] - points[0][0])}" height="${Math.abs(points[1][1] - points[0][1])}"${transAttribute}/>`;
|
||||
|
||||
} else if (this.shape == Shapes.TEXT && points.length == 2) {
|
||||
let transAttribute = "";
|
||||
if (this.transform.angle != 0) {
|
||||
let angle = this.transform.angle * 180 / Math.PI;
|
||||
transAttribute = ` transform="rotate(${angle}, ${this.transform.center[0]}, ${this.transform.center[1]})"`;
|
||||
}
|
||||
attributes = `fill="${color}" ` +
|
||||
`stroke="transparent" ` +
|
||||
`stroke-opacity="0" ` +
|
||||
|
|
@ -607,7 +654,7 @@ var DrawingElement = new Lang.Class({
|
|||
`font-weight="${FontWeightNames[this.font.weight].toLowerCase()}" ` +
|
||||
`font-style="${FontStyleNames[this.font.style].toLowerCase()}"`;
|
||||
|
||||
row += `<text ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.max(points[0][1], points[1][1])}">${this.text}</text>`;
|
||||
row += `<text ${attributes}${transAttribute} x="${Math.min(points[0][0], points[1][0])}" y="${Math.max(points[0][1], points[1][1])}">${this.text}</text>`;
|
||||
}
|
||||
|
||||
return row;
|
||||
|
|
@ -630,8 +677,78 @@ var DrawingElement = new Lang.Class({
|
|||
this.smooth(i);
|
||||
}
|
||||
},
|
||||
|
||||
rotate: function(cr, angle, x, y) {
|
||||
if (angle == 0)
|
||||
return;
|
||||
cr.translate(x, y);
|
||||
cr.rotate(angle);
|
||||
cr.translate(-x, -y);
|
||||
},
|
||||
|
||||
scale: function(cr, ratio, x, y) {
|
||||
if (ratio == 1)
|
||||
return;
|
||||
cr.translate(x, y);
|
||||
cr.scale(ratio, 1);
|
||||
cr.translate(-x, -y);
|
||||
},
|
||||
|
||||
transformRectangle: function(x, y) {
|
||||
let points = this.points;
|
||||
if (points.length < 2 || points[0][0] == points[1][0] || points[0][1] == points[1][1])
|
||||
return;
|
||||
|
||||
this.transform.center = [points[0][0] + (points[1][0] - points[0][0]) / 2, points[0][1] + (points[1][1] - points[0][1]) / 2];
|
||||
|
||||
this.transform.angle = getAngle(this.transform.center[0], this.transform.center[1], points[1][0], points[1][1], x, y);
|
||||
this.transform.active = true;
|
||||
},
|
||||
|
||||
transformEllipse: function(x, y) {
|
||||
let points = this.points;
|
||||
if (points.length < 2 || points[0][0] == points[1][0] || points[0][1] == points[1][1])
|
||||
return;
|
||||
|
||||
this.transform.center = [points[0][0], points[0][1]];
|
||||
|
||||
let r1 = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||
let r2 = Math.hypot(x - points[0][0], y - points[0][1]);
|
||||
this.transform.ratio = r2 / r1;
|
||||
|
||||
this.transform.angle = getAngle(this.transform.center[0], this.transform.center[1], points[1][0], points[1][1], x, y);
|
||||
if (!this.transform.startAngle)
|
||||
// that is the angle between the direction when starting ellipticalizing, and the x-axis
|
||||
this.transform.startAngle = getAngle(points[0][0], points[0][1], points[0][0] + 1, points[0][1], points[1][0], points[1][1]);
|
||||
this.transform.active = true;
|
||||
},
|
||||
|
||||
transformLine: function(x, y) {
|
||||
if (this.points.length < 2)
|
||||
return;
|
||||
if (this.points.length == 2)
|
||||
this.points[2] = this.points[1];
|
||||
this.points[1] = [x, y];
|
||||
this.transform.active = true;
|
||||
},
|
||||
});
|
||||
|
||||
function getAngle(xO, yO, xA, yA, xB, yB) {
|
||||
// calculate angle of rotation in absolute value
|
||||
// cos(AOB) = (OA.OB)/(||OA||*||OB||) where OA.OB = (xA-xO)*(xB-xO) + (yA-yO)*(yB-yO)
|
||||
let angle = Math.acos( ((xA - xO)*(xB - xO) + (yA - yO)*(yB - yO)) / (Math.hypot(xA - xO, yA - yO) * Math.hypot(xB - xO, yB - yO)) );
|
||||
|
||||
// determine the sign of the angle
|
||||
// equation of OA: y = ax + b
|
||||
let a = (yA - yO) / (xA - xO);
|
||||
let b = yA - a*xA;
|
||||
if (yB < a*xB + b)
|
||||
angle = - angle;
|
||||
if (xA < xO)
|
||||
angle = - angle;
|
||||
return angle;
|
||||
}
|
||||
|
||||
var HELPER_ANIMATION_TIME = 0.25;
|
||||
|
||||
// DrawingHelper provides the "help osd" (Ctrl + F1)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ msgstr ""
|
|||
msgid "Line"
|
||||
msgstr ""
|
||||
|
||||
msgid "Circle"
|
||||
msgid "Ellipse"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rectangle"
|
||||
|
|
@ -55,12 +55,17 @@ msgid ""
|
|||
msgstr ""
|
||||
|
||||
#: prefs.js
|
||||
|
||||
# GLOBAL_KEYBINDINGS
|
||||
|
||||
msgid "Enter/leave drawing mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Erase all drawings"
|
||||
msgstr ""
|
||||
|
||||
# INTERNAL_KEYBINDINGS
|
||||
|
||||
msgid "Undo last brushstroke"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -91,13 +96,14 @@ msgstr ""
|
|||
msgid "Change linecap"
|
||||
msgstr ""
|
||||
|
||||
# already in draw.js
|
||||
#msgid "Dashed line"
|
||||
#msgstr ""
|
||||
|
||||
msgid "Select line"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select circle"
|
||||
msgid "Select ellipse"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select rectangle"
|
||||
|
|
@ -133,6 +139,8 @@ msgstr ""
|
|||
msgid "Show help"
|
||||
msgstr ""
|
||||
|
||||
# OTHER_SHORTCUTS
|
||||
|
||||
msgid "Draw"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -151,6 +159,12 @@ msgstr ""
|
|||
msgid "Center click"
|
||||
msgstr ""
|
||||
|
||||
msgid "Transform shape (when drawing)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ctrl key"
|
||||
msgstr ""
|
||||
|
||||
msgid "Increment/decrement line width"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -175,6 +189,8 @@ msgstr ""
|
|||
msgid "Escape key"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
|
||||
msgid ""
|
||||
"Start drawing with Super+Alt+D\n"
|
||||
"Then save your beautiful work by taking a screenshot"
|
||||
|
|
@ -189,6 +205,13 @@ msgstr ""
|
|||
msgid "(in drawing mode)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"By pressing <b>Ctrl</b> key <b>during</b> the drawing process, you can:\n"
|
||||
" . rotate a rectangle or a text area\n"
|
||||
" . extend and rotate an ellipse\n"
|
||||
" . curve a line (cubic Bezier curve)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Smooth stroke during the drawing process"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
20
prefs.js
20
prefs.js
|
|
@ -52,7 +52,7 @@ var INTERNAL_KEYBINDINGS = {
|
|||
'toggle-dash': "Dashed line",
|
||||
'-separator-2': '',
|
||||
'select-line-shape': "Select line",
|
||||
'select-ellipse-shape': "Select circle",
|
||||
'select-ellipse-shape': "Select ellipse",
|
||||
'select-rectangle-shape': "Select rectangle",
|
||||
'select-text-shape': "Select text",
|
||||
'select-none-shape': "Unselect shape (free drawing)",
|
||||
|
|
@ -73,6 +73,7 @@ var OTHER_SHORTCUTS = [
|
|||
{ desc: "Draw", shortcut: "Left click" },
|
||||
{ desc: "Draw by filling in", shortcut: "Right click" },
|
||||
{ desc: "Toggle shape", shortcut: "Center click" },
|
||||
{ desc: "Transform shape (when drawing)", shortcut: "Ctrl key" },
|
||||
{ desc: "Increment/decrement line width", shortcut: "Scroll" },
|
||||
{ desc: "Select color", shortcut: "Ctrl+1...9" },
|
||||
{ desc: "Select eraser", shortcut: "Shift key held" },
|
||||
|
|
@ -104,7 +105,7 @@ const PrefsPage = new GObject.Class({
|
|||
|
||||
let textBox1 = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN });
|
||||
let text1 = new Gtk.Label({ wrap: true, justify: 2, use_markup: true,
|
||||
label: _("Start drawing with Super+Alt+D\nThen save your beautiful work by taking a screenshot") });
|
||||
label: "<big> " + _("Start drawing with Super+Alt+D\nThen save your beautiful work by taking a screenshot") + "</big>" });
|
||||
textBox1.pack_start(text1, false, false, 0);
|
||||
box.add(textBox1);
|
||||
|
||||
|
|
@ -149,6 +150,16 @@ const PrefsPage = new GObject.Class({
|
|||
|
||||
listBox.add(new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN }));
|
||||
|
||||
let controlBox = new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN, margin_bottom:MARGIN });
|
||||
let controlLabel = new Gtk.Label({
|
||||
use_markup: true,
|
||||
label: _("By pressing <b>Ctrl</b> key <b>during</b> the drawing process, you can:\n . rotate a rectangle or a text area\n . extend and rotate an ellipse\n . curve a line (cubic Bezier curve)")
|
||||
});
|
||||
controlLabel.set_halign(1);
|
||||
controlLabel.get_style_context().add_class("dim-label");
|
||||
controlBox.pack_start(controlLabel, true, true, 4);
|
||||
listBox.add(controlBox);
|
||||
|
||||
let smoothBox = new Gtk.Box({ margin: MARGIN });
|
||||
let smoothLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
|
||||
let smoothLabel1 = new Gtk.Label({label: _("Smooth stroke during the drawing process")});
|
||||
|
|
@ -178,12 +189,11 @@ const PrefsPage = new GObject.Class({
|
|||
let noteBox = new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN, margin_bottom:MARGIN });
|
||||
let noteLabel = new Gtk.Label({
|
||||
use_markup: true,
|
||||
label: _("<u>Note</u>: When you save elements made with eraser in a SVG file,\nthey are colored with background color, transparent if it is disabled.\n(See \"Add a drawing background\" or edit the SVG file afterwards)")
|
||||
label: _("<u>Note</u>: When you save elements made with <b>eraser</b> in a <b>SVG</b> file,\nthey are colored with background color, transparent if it is disabled.\n(See \"Add a drawing background\" or edit the SVG file afterwards)")
|
||||
});
|
||||
noteLabel.set_halign(1);
|
||||
//let noteLabel2 = new Gtk.Label({ label: _("See notesheet.css") });
|
||||
noteLabel.get_style_context().add_class("dim-label");
|
||||
noteBox.pack_start(noteLabel, true, true, 4);
|
||||
//noteBox.pack_start(noteLabel2, false, false, 4);
|
||||
listBox.add(noteBox);
|
||||
|
||||
this.addSeparator(listBox);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
}
|
||||
|
||||
.draw-on-your-screen-helper StBoxLayout {
|
||||
spacing: 5em;
|
||||
spacing: 7em;
|
||||
}
|
||||
|
||||
.draw-on-your-screen-helper StBoxLayout StLabel {
|
||||
|
|
|
|||
Loading…
Reference in New Issue