new image shape

Images files are picked from `~/DrawOnYourScreen/images/` (and `extensiondir/images/`)
svg, png, jpeg, ...(pixbuf)
This commit is contained in:
abakkk 2020-07-30 11:13:23 +02:00
parent 7a41c6157c
commit 1172e9da6f
11 changed files with 372 additions and 30 deletions

View File

@ -7,7 +7,7 @@ Then save your beautiful work by taking a screenshot.
## Features ## Features
* Basic shapes (rectangle, circle, ellipse, line, curve, text, free) * Basic shapes (rectangle, circle, ellipse, line, curve, text, image, free)
* Basic transformations (move, rotate, resize, stretch, mirror, inverse) * Basic transformations (move, rotate, resize, stretch, mirror, inverse)
* Smooth stroke * Smooth stroke
* Draw over applications * Draw over applications
@ -40,6 +40,10 @@ Then save your beautiful work by taking a screenshot.
![How to duplicate an element](https://framagit.org/abakkk/DrawOnYourScreen/-/raw/ressources/duplicate.webm) ![How to duplicate an element](https://framagit.org/abakkk/DrawOnYourScreen/-/raw/ressources/duplicate.webm)
* Insertable images:
Add your images (jpeg, png, svg) to `~/.local/share/drawOnYourScreen/images/`.
* Screenshot Tool extension: * Screenshot Tool extension:
[Screenshot Tool](https://extensions.gnome.org/extension/1112/screenshot-tool/) is a convenient extension to “create, copy, store and upload screenshots”. In order to select a screenshoot area with your pointer while keeping the drawing in place, you need first to tell DrawOnYourScreen to ungrab the pointer (`Ctrl + Super + Alt + D`). [Screenshot Tool](https://extensions.gnome.org/extension/1112/screenshot-tool/) is a convenient extension to “create, copy, store and upload screenshots”. In order to select a screenshoot area with your pointer while keeping the drawing in place, you need first to tell DrawOnYourScreen to ungrab the pointer (`Ctrl + Super + Alt + D`).

56
area.js
View File

@ -30,6 +30,7 @@ const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const St = imports.gi.St; const St = imports.gi.St;
const System = imports.system;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -39,6 +40,7 @@ const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience; const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience;
const Extension = Me.imports.extension; const Extension = Me.imports.extension;
const Elements = Me.imports.elements; const Elements = Me.imports.elements;
const Files = Me.imports.files;
const Menu = Me.imports.menu; const Menu = Me.imports.menu;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
@ -46,10 +48,13 @@ const CAIRO_DEBUG_EXTENDS = false;
const SVG_DEBUG_EXTENDS = false; const SVG_DEBUG_EXTENDS = false;
const TEXT_CURSOR_TIME = 600; // ms const TEXT_CURSOR_TIME = 600; // ms
const { Shapes, Manipulations, Transformations, LineCapNames, LineJoinNames, FillRuleNames, const { Shapes, ShapeNames, Transformations, LineCapNames, LineJoinNames, FillRuleNames,
FontWeightNames, FontStyleNames, FontStretchNames, FontVariantNames } = Elements; FontWeightNames, FontStyleNames, FontStretchNames, FontVariantNames } = Elements;
const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 };
const ManipulationNames = { 100: "Move", 101: "Resize", 102: "Mirror" };
var Tools = Object.assign({}, Shapes, Manipulations); var Tools = Object.assign({}, Shapes, Manipulations);
var ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" }; var ToolNames = Object.assign({}, ShapeNames, ManipulationNames);
var FontGenericNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; var FontGenericNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' };
var getDateString = function() { var getDateString = function() {
@ -96,6 +101,7 @@ var DrawingArea = new Lang.Class({
Name: 'DrawOnYourScreenDrawingArea', Name: 'DrawOnYourScreenDrawingArea',
Extends: St.DrawingArea, Extends: St.DrawingArea,
Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] },
'show-osd-gicon': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] },
'update-action-mode': {}, 'update-action-mode': {},
'leave-drawing-mode': {} }, 'leave-drawing-mode': {} },
@ -113,6 +119,7 @@ var DrawingArea = new Lang.Class({
this.undoneElements = []; this.undoneElements = [];
this.currentElement = null; this.currentElement = null;
this.currentTool = Shapes.NONE; this.currentTool = Shapes.NONE;
this.currentImage = 0;
this.currentFontGeneric = 0; this.currentFontGeneric = 0;
this.isSquareArea = false; this.isSquareArea = false;
this.hasGrid = false; this.hasGrid = false;
@ -169,6 +176,13 @@ var DrawingArea = new Lang.Class({
this.currentFillRule = evenodd ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING; this.currentFillRule = evenodd ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING;
}, },
getImages() {
let images = Files.getImages();
if (!images[this.currentImage])
this.currentImage = Math.max(images.length - 1, 0);
return images;
},
vfunc_repaint: function() { vfunc_repaint: function() {
let cr = this.get_context(); let cr = this.get_context();
@ -179,6 +193,8 @@ var DrawingArea = new Lang.Class({
} }
cr.$dispose(); cr.$dispose();
if (this.elements.some(element => element.shape == Shapes.IMAGE) || this.currentElement && this.currentElement.shape == Shapes.IMAGE)
System.gc();
}, },
_redisplay: function() { _redisplay: function() {
@ -266,7 +282,7 @@ var DrawingArea = new Lang.Class({
if (this.currentElement) { if (this.currentElement) {
cr.save(); cr.save();
this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor,
showTextRectangle: this.currentElement.shape == Shapes.TEXT && !this.isWriting, showTextRectangle: this.currentElement.shape != Shapes.TEXT || !this.isWriting,
dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 });
cr.stroke(); cr.stroke();
@ -477,6 +493,8 @@ var DrawingArea = new Lang.Class({
if (duplicate) { if (duplicate) {
// deep cloning // deep cloning
let copy = new this.grabbedElement.constructor(JSON.parse(JSON.stringify(this.grabbedElement))); let copy = new this.grabbedElement.constructor(JSON.parse(JSON.stringify(this.grabbedElement)));
if (this.grabbedElement.image)
copy.image = this.grabbedElement.image;
this.elements.push(copy); this.elements.push(copy);
this.grabbedElement = copy; this.grabbedElement = copy;
} }
@ -574,6 +592,18 @@ var DrawingArea = new Lang.Class({
textRightAligned: this.currentTextRightAligned, textRightAligned: this.currentTextRightAligned,
points: [] points: []
}); });
} else if (this.currentTool == Shapes.IMAGE) {
let images = this.getImages();
if (!images.length)
return;
this.currentElement = new Elements.DrawingElement({
shape: this.currentTool,
color: this.currentColor.to_string(),
eraser: eraser,
image: images[this.currentImage],
operator: this.currentOperator,
points: []
});
} else { } else {
this.currentElement = new Elements.DrawingElement({ this.currentElement = new Elements.DrawingElement({
shape: this.currentTool, shape: this.currentTool,
@ -939,6 +969,15 @@ var DrawingArea = new Lang.Class({
this.emit('show-osd', null, this.currentTextRightAligned ? _("Right aligned") : _("Left aligned"), "", -1, false); this.emit('show-osd', null, this.currentTextRightAligned ? _("Right aligned") : _("Left aligned"), "", -1, false);
}, },
toggleImageFile: function() {
let images = this.getImages();
if (!images.length)
return;
if (images.length > 1)
this.currentImage = this.currentImage == images.length - 1 ? 0 : this.currentImage + 1;
this.emit('show-osd-gicon', images[this.currentImage].gicon, images[this.currentImage].toString(), "", -1, false);
},
toggleHelp: function() { toggleHelp: function() {
if (this.helper.visible) { if (this.helper.visible) {
this.helper.hideHelp(); this.helper.hideHelp();
@ -1034,7 +1073,10 @@ var DrawingArea = new Lang.Class({
this._stopDrawing(); this._stopDrawing();
} }
let content = `<svg viewBox="0 0 ${this.width} ${this.height}" xmlns="http://www.w3.org/2000/svg">`; let prefixes = 'xmlns="http://www.w3.org/2000/svg"';
if (this.elements.some(element => element.shape == Shapes.IMAGE))
prefixes += ' xmlns:xlink="http://www.w3.org/1999/xlink"';
let content = `<svg viewBox="0 0 ${this.width} ${this.height}" ${prefixes}>`;
if (SVG_DEBUG_EXTENDS) if (SVG_DEBUG_EXTENDS)
content = `<svg viewBox="${-this.width} ${-this.height} ${2 * this.width} ${2 * this.height}" xmlns="http://www.w3.org/2000/svg">`; content = `<svg viewBox="${-this.width} ${-this.height} ${2 * this.width} ${2 * this.height}" xmlns="http://www.w3.org/2000/svg">`;
let backgroundColorString = this.hasBackground ? this.activeBackgroundColor.to_string() : 'transparent'; let backgroundColorString = this.hasBackground ? this.activeBackgroundColor.to_string() : 'transparent';
@ -1156,7 +1198,11 @@ var DrawingArea = new Lang.Class({
return; return;
if (contents instanceof Uint8Array) if (contents instanceof Uint8Array)
contents = ByteArray.toString(contents); contents = ByteArray.toString(contents);
this.elements.push(...JSON.parse(contents).map(object => new Elements.DrawingElement(object))); this.elements.push(...JSON.parse(contents).map(object => {
if (object.image)
object.image = new Files.Image(object.image);
return new Elements.DrawingElement(object);
}));
if (notify) if (notify)
this.emit('show-osd', 'document-open-symbolic', name, "", -1, false); this.emit('show-osd', 'document-open-symbolic', name, "", -1, false);

43
data/images/Example.svg Normal file
View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<!-- https://commons.wikimedia.org/wiki/File:Bananas.svg -->
<!-- public domain -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="559.62469" height="373.29596" id="svg2" sodipodi:version="0.32" inkscape:version="0.44" version="1.0" sodipodi:docbase="C:\Documents and Settings\Elembis\My Documents\Images\Shartak\Shartak.com" sodipodi:docname="bananas3.svg" inkscape:export-filename="C:\Documents and Settings\Elembis\My Documents\Images\Shartak\Shartak.com\banana.png" inkscape:export-xdpi="25.797226" inkscape:export-ydpi="25.797226">
<defs id="defs4"/>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" gridtolerance="10000" guidetolerance="10" objecttolerance="10" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" inkscape:cx="257.85399" inkscape:cy="231.60006" inkscape:document-units="px" inkscape:current-layer="layer1" inkscape:window-width="1024" inkscape:window-height="721" inkscape:window-x="-4" inkscape:window-y="-4"/>
<metadata id="metadata7">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-66.38047,-391.3222)">
<g id="g9893" inkscape:transform-center-x="88.0002" inkscape:transform-center-y="113.00018">
<path transform="translate(-17.85713,318.6479)" sodipodi:nodetypes="csccccc" id="path17976" d="M 543,119 C 543,119 547,177 531,189 C 515,201 397,320 397,320 L 419,345 L 562,248 L 573,121 L 543,119 z " style="fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path sodipodi:nodetypes="csccccc" id="path18863" d="M 525.14247,437.64826 C 525.14247,437.64826 529.14247,495.64826 513.14247,507.64826 C 497.14247,519.64826 379.14247,638.64826 379.14247,638.64826 L 401.14247,663.64826 L 544.14247,566.64826 L 555.14247,439.64826 L 525.14247,437.64826 z " style="opacity:0.7;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
<g id="g2783">
<path id="path6424" d="M 519.9114,399.65406 L 483.38679,454.45323 C 483.38679,454.45323 454.12626,461.72103 439.29443,470.94146 C 424.46259,480.16189 377.85779,514.19114 274.74769,506.89067 C 171.63759,499.59021 156.32242,477.55739 133.97629,476.75109 C 111.63016,475.9448 77.83775,479.82705 69.64709,486.06507 C 61.45643,492.3031 71.25003,511.09366 71.25003,511.09366 C 71.25003,511.09366 101.21736,578.4951 210.30564,606.14897 C 319.39391,633.80284 448.5453,576.70729 448.5453,576.70729 L 538.74205,411.07361 L 519.9114,399.65406 z " style="fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path id="path1895" d="M 517.00522,404.00971 L 483.38022,454.44721 C 483.38022,454.44721 454.1183,461.72678 439.28647,470.94721 C 424.45462,480.16764 377.86532,514.18518 274.75522,506.88471 C 171.64512,499.58425 156.3201,477.56601 133.97397,476.75971 C 111.62785,475.95343 77.85213,479.83419 69.66147,486.07221 C 69.2897,486.35536 68.96174,486.67778 68.66147,487.00971 C 87.94596,484.21174 116.37515,484.78053 136.38022,488.41596 C 174.77085,495.39252 209.97775,518.65573 299.50522,516.22846 C 426.73709,512.73998 441.6649,473.10304 459.38022,468.41596 C 484.00837,461.89991 530.48444,452.34626 517.00522,404.00971 z " style="fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;opacity:0.4"/>
<path id="path9086" d="M 106.96213,554.70796 C 128.49383,573.41467 161.44348,593.76255 210.30593,606.14918 C 222.07021,609.13143 234.0755,611.11073 246.11929,612.29962 L 284.83188,604.45269 C 220.5418,614.51673 142.31641,575.18761 106.96213,554.70796 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
<g id="g9941">
<path transform="translate(-17.85713,318.6479)" inkscape:transform-center-y="181" inkscape:transform-center-x="126" sodipodi:nodetypes="cssssssssc" id="path12644" d="M 546,120 C 546,120 567,175 561,189 C 555,203 520,283 455,313 C 390,343 223,339 219,331 C 215,323 219,407 320,438 C 421,469 550.08904,401.09271 573,379 C 602,351 657,279 641,225 C 625,171 613,187 604,172 C 595,157 585,128 585,116 C 585,104 547,120 546,120 z " style="opacity:1;fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path id="path8981" d="M 533.7244,527.63751 C 518.52971,557.28537 486.49949,608.85189 437.13065,631.63751 C 372.13066,661.63751 205.13065,657.63751 201.13065,649.63751 C 200.13065,647.63751 199.64628,651.40313 201.0369,658.63751 L 201.06815,658.76251 L 203.13065,665.63751 C 203.13064,665.63751 202.9682,665.65254 202.75565,665.73126 C 202.85933,666.09273 202.98841,666.48596 203.0994,666.85626 C 203.2463,667.34553 203.37675,667.82087 203.5369,668.32501 C 203.82494,669.23173 204.17292,670.18568 204.50565,671.13751 C 204.61749,671.45746 204.70108,671.78151 204.81815,672.10626 C 205.04405,672.73471 205.32226,673.36688 205.56815,674.01251 C 205.8517,674.75499 206.10087,675.49879 206.4119,676.26251 C 206.6323,676.80515 206.89597,677.36609 207.13065,677.91876 C 207.44564,678.65842 207.75805,679.38112 208.0994,680.13751 C 208.26157,680.49795 208.4311,680.86725 208.5994,681.23126 C 209.69117,683.59259 210.89044,686.02408 212.25565,688.51251 C 212.29126,688.57728 212.34485,688.63516 212.38065,688.70001 C 212.39669,688.72911 212.39582,688.76464 212.4119,688.79376 C 212.77389,689.44818 213.15496,690.10127 213.5369,690.76251 C 213.57214,690.82361 213.59523,690.88885 213.63065,690.95001 C 214.03425,691.64604 214.45442,692.3411 214.88065,693.04376 C 215.33612,693.79463 215.80496,694.56759 216.2869,695.32501 C 216.29464,695.33717 216.3104,695.34409 216.31815,695.35626 C 216.77977,696.08134 217.23774,696.81355 217.7244,697.54376 C 217.72759,697.54854 217.75246,697.53898 217.75565,697.54376 C 258.55477,715.44577 403.2244,681.10626 468.13065,633.63751 C 513.82083,600.22229 528.80654,553.79612 533.7244,527.63751 z " style="opacity:0.6;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path id="path17089" d="M 559.79937,430.17885 C 558.18509,430.20703 556.38497,430.42195 554.48687,430.74135 C 560.75792,449.99636 590.83405,543.70808 588.14312,561.6476 C 585.14312,581.6476 553.30943,654.34782 501.14312,685.6476 C 466.14312,706.6476 390.14312,739.6476 363.14312,744.6476 C 346.05557,747.81196 312.61613,752.46521 286.48687,751.11635 C 291.4351,753.10006 296.63167,754.95597 302.14312,756.6476 C 403.14312,787.6476 532.23216,719.74031 555.14312,697.6476 C 584.14312,669.6476 639.14312,597.6476 623.14312,543.6476 C 607.14312,489.6476 595.14312,505.6476 586.14312,490.6476 C 577.14312,475.6476 567.14312,446.6476 567.14312,434.6476 C 567.14312,431.2726 564.14254,430.10304 559.79937,430.17885 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
<g id="g9903">
<path sodipodi:nodetypes="cssccccc" id="path1933" d="M 533.14287,448.6479 C 533.14287,448.6479 514.14287,467.6479 519.14287,485.6479 C 524.14287,503.6479 541.14287,547.6479 478.14287,597.6479 C 415.14287,647.6479 232.14287,734.6479 109.14287,639.6479 C 74.14287,599.6479 94.14287,573.6479 94.14287,573.6479 C 94.14287,573.6479 114.12992,570.75757 122.14287,571.6479 C 132.14287,568.6479 179.14287,571.6479 223.14287,573.6479 C 282.05327,572.49008 443.14287,497.6479 474.14287,468.6479 C 474.14287,468.6479 512.14287,422.6479 513.14287,414.6479 C 514.14287,406.6479 540.14287,430.6479 541.14287,437.6479 C 542.14287,444.6479 533.14287,447.6479 533.14287,448.6479 z " style="fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path sodipodi:nodetypes="cssccccsscc" id="path7201" d="M 514.60028,413.04467 C 513.79169,413.14037 513.25653,413.63842 513.13153,414.63842 C 512.13153,422.63842 474.13153,468.63841 474.13153,468.63842 C 443.13153,497.63842 282.04191,572.4806 223.13152,573.63842 C 179.13152,571.63842 132.13152,568.63842 122.13152,571.63842 C 114.11858,570.74809 94.13152,573.63842 94.13152,573.63842 C 94.13152,573.63842 93.7656,574.16602 93.22527,575.07592 C 104.18437,578.28284 124.12588,582.75014 156.38152,583.60717 C 230.77215,585.58373 262.00653,581.54467 326.50653,556.41967 C 394.13686,530.0753 484.82418,496.21955 520.38153,415.35717 C 517.97095,413.81698 515.90107,412.89071 514.60028,413.04467 z " style="opacity:0.4;fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path id="path4646" d="M 518.58062,482.8981 C 510.39544,511.03311 477.26645,585.47414 350.14312,628.6481 C 194.69709,681.44109 156.81818,651.08254 106.61187,636.61685 C 107.43132,637.61484 108.2539,638.63184 109.14312,639.6481 C 232.14312,734.6481 415.14312,647.6481 478.14312,597.6481 C 541.14312,547.6481 524.14312,503.6481 519.14312,485.6481 C 518.88851,484.73149 518.71644,483.81726 518.58062,482.8981 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
<g id="g9946">
<path sodipodi:nodetypes="czsssz" id="path23294" d="M 534.47367,391.42537 C 528.72367,389.92537 508.71616,405.72925 509.06846,411.02017 C 509.42029,416.30405 528.53454,433.43637 540.64281,437.57407 C 544.64281,438.57407 566.64281,436.57407 567.64281,432.57407 C 568.64281,428.57407 568.64281,411.57407 564.64281,407.57407 C 560.64281,403.57407 541.16441,393.17078 534.47367,391.42537 z " style="fill:#884a13;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path sodipodi:nodetypes="czsssz" id="path22407" d="M 534.47374,391.42502 C 528.72374,389.92502 508.71623,405.7289 509.06853,411.01982 C 509.42036,416.3037 528.53461,433.43602 540.64288,437.57372 C 544.64288,438.57372 566.64288,436.57372 567.64288,432.57372 C 568.64288,428.57372 568.64288,411.57372 564.64288,407.57372 C 560.64288,403.57372 541.16448,393.17043 534.47374,391.42502 z " style="fill:#884a13;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path id="path25958" d="M 533.23672,391.3354 C 526.30206,391.85508 508.75019,406.06266 509.08047,411.0229 C 509.222,413.14841 512.41917,417.17996 516.95547,421.5229 C 517.24178,413.18494 521.37892,401.08827 540.67422,393.7729 C 538.17765,392.66554 536.00097,391.82417 534.48672,391.42915 C 534.12734,391.3354 533.69903,391.30075 533.23672,391.3354 z " style="opacity:0.3;fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path id="path26854" d="M 564.58087,407.52257 C 564.73328,425.7813 549.04695,433.48138 538.20587,436.61632 C 539.04145,436.98636 539.85952,437.31721 540.64337,437.58507 C 544.64338,438.58507 566.64337,436.58507 567.64337,432.58507 C 568.64338,428.58507 568.64337,411.58507 564.64337,407.58507 C 564.62587,407.56758 564.59896,407.54031 564.58087,407.52257 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -34,8 +34,8 @@ const reverseEnumeration = function(obj) {
return reversed; return reversed;
}; };
var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 };
var Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 7: "Image" };
var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 }; var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 };
var LineCapNames = Object.assign(reverseEnumeration(Cairo.LineCap), { 2: 'Square' }); var LineCapNames = Object.assign(reverseEnumeration(Cairo.LineCap), { 2: 'Square' });
var LineJoinNames = reverseEnumeration(Cairo.LineJoin); var LineJoinNames = reverseEnumeration(Cairo.LineJoin);
@ -46,7 +46,6 @@ var FontStyleNames = reverseEnumeration(Pango.Style);
var FontStretchNames = reverseEnumeration(Pango.Stretch); var FontStretchNames = reverseEnumeration(Pango.Stretch);
var FontVariantNames = reverseEnumeration(Pango.Variant); var FontVariantNames = reverseEnumeration(Pango.Variant);
const SVG_DEBUG_SUPERPOSES_CAIRO = false; const SVG_DEBUG_SUPERPOSES_CAIRO = false;
const RADIAN = 180 / Math.PI; // degree const RADIAN = 180 / Math.PI; // degree
const INVERSION_CIRCLE_RADIUS = 12; // px const INVERSION_CIRCLE_RADIUS = 12; // px
@ -58,7 +57,9 @@ const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad
const MIN_DRAWING_SIZE = 3; // px const MIN_DRAWING_SIZE = 3; // px
var DrawingElement = function(params) { var DrawingElement = function(params) {
return params.shape == Shapes.TEXT ? new TextElement(params) : new _DrawingElement(params); return params.shape == Shapes.TEXT ? new TextElement(params) :
params.shape == Shapes.IMAGE ? new ImageElement(params) :
new _DrawingElement(params);
}; };
// DrawingElement represents a "brushstroke". // DrawingElement represents a "brushstroke".
@ -109,9 +110,11 @@ const _DrawingElement = new Lang.Class({
}, },
buildCairo: function(cr, params) { buildCairo: function(cr, params) {
let [success, color] = Clutter.Color.from_string(this.color); if (this.color) {
if (success) let [success, color] = Clutter.Color.from_string(this.color);
Clutter.cairo_set_source_color(cr, color); if (success)
Clutter.cairo_set_source_color(cr, color);
}
if (this.showSymmetryElement) { if (this.showSymmetryElement) {
let transformation = this.lastTransformation; let transformation = this.lastTransformation;
@ -726,6 +729,85 @@ const TextElement = new Lang.Class({
}, },
}); });
const ImageElement = new Lang.Class({
Name: 'DrawOnYourScreenImageElement',
Extends: _DrawingElement,
_init: function(params) {
params.fill = false;
this.parent(params);
},
toJSON: function() {
return {
shape: this.shape,
color: this.color,
fill: this.fill,
eraser: this.eraser,
transformations: this.transformations,
image: this.image.toJson(),
preserveAspectRatio: this.preserveAspectRatio,
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
};
},
_drawCairo: function(cr, params) {
if (this.points.length < 2)
return;
let points = this.points;
let [x, y] = [Math.min(points[0][0], points[1][0]), Math.min(points[0][1], points[1][1])];
let [width, height] = [Math.abs(points[1][0] - points[0][0]), Math.abs(points[1][1] - points[0][1])];
if (width < 1 || height < 1)
return;
cr.save();
this.image.setCairoSource(cr, x, y, width, height, this.preserveAspectRatio);
cr.rectangle(x, y, width, height);
cr.fill();
cr.restore();
if (params.showTextRectangle) {
cr.rectangle(x, y, width, height);
setDummyStroke(cr);
} else if (params.drawTextRectangle) {
cr.rectangle(x, y, width, height);
// Only draw the rectangle to find the element, not to show it.
cr.setLineWidth(0);
}
},
getContainsPoint: function(cr, x, y) {
return cr.inFill(x, y);
},
_drawSvg: function(transAttribute) {
let points = this.points;
let row = "\n ";
let attributes = '';
if (points.length == 2) {
attributes = `fill="none"`;
row += `<image ${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])}"${transAttribute} ` +
`preserveAspectRatio="${this.preserveAspectRatio ? 'xMinYMin' : 'none'}" ` +
`id="${this.image.displayName}" xlink:href="data:${this.image.contentType};base64,${this.image.base64}"/>`;
}
return row;
},
updateDrawing: function(x, y, transform) {
let points = this.points;
if (x == points[0][0] || y == points[0][1])
return;
points[1] = [x, y];
this.preserveAspectRatio = !transform;
}
});
const setDummyStroke = function(cr) { const setDummyStroke = function(cr) {
cr.setLineWidth(2); cr.setLineWidth(2);
cr.setLineCap(0); cr.setLineCap(0);

View File

@ -171,6 +171,7 @@ var AreaManager = new Lang.Class({
area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this)); area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this));
area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this)); area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this));
area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this)); area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this));
area.showOsdGiconHandler = area.connect('show-osd-gicon', this.showOsd.bind(this));
this.areas.push(area); this.areas.push(area);
} }
}, },
@ -191,11 +192,13 @@ var AreaManager = new Lang.Class({
'toggle-fill-rule': this.activeArea.toggleFillRule.bind(this.activeArea), 'toggle-fill-rule': this.activeArea.toggleFillRule.bind(this.activeArea),
'toggle-dash' : this.activeArea.toggleDash.bind(this.activeArea), 'toggle-dash' : this.activeArea.toggleDash.bind(this.activeArea),
'toggle-fill' : this.activeArea.toggleFill.bind(this.activeArea), 'toggle-fill' : this.activeArea.toggleFill.bind(this.activeArea),
'toggle-image-file' : this.activeArea.toggleImageFile.bind(this.activeArea),
'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE), 'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE),
'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE), 'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE),
'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE), 'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE),
'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tools.RECTANGLE), 'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tools.RECTANGLE),
'select-text-shape': () => this.activeArea.selectTool(Area.Tools.TEXT), 'select-text-shape': () => this.activeArea.selectTool(Area.Tools.TEXT),
'select-image-shape': () => this.activeArea.selectTool(Area.Tools.IMAGE),
'select-polygon-shape': () => this.activeArea.selectTool(Area.Tools.POLYGON), 'select-polygon-shape': () => this.activeArea.selectTool(Area.Tools.POLYGON),
'select-polyline-shape': () => this.activeArea.selectTool(Area.Tools.POLYLINE), 'select-polyline-shape': () => this.activeArea.selectTool(Area.Tools.POLYLINE),
'select-move-tool': () => this.activeArea.selectTool(Area.Tools.MOVE), 'select-move-tool': () => this.activeArea.selectTool(Area.Tools.MOVE),
@ -505,6 +508,7 @@ var AreaManager = new Lang.Class({
area.disconnect(area.leaveDrawingHandler); area.disconnect(area.leaveDrawingHandler);
area.disconnect(area.updateActionModeHandler); area.disconnect(area.updateActionModeHandler);
area.disconnect(area.showOsdHandler); area.disconnect(area.showOsdHandler);
area.disconnect(area.showOsdGiconHandler);
let container = area.get_parent(); let container = area.get_parent();
container.get_parent().remove_actor(container); container.get_parent().remove_actor(container);
container.destroy(); container.destroy();

132
files.js Normal file
View File

@ -0,0 +1,132 @@
/* jslint esversion: 6 */
/*
* Copyright 2019 Abakkk
*
* This file is part of DrawOnYourScreen, a drawing extension for GNOME Shell.
* https://framagit.org/abakkk/DrawOnYourScreen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const Gdk = imports.gi.Gdk;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const EXAMPLE_IMAGES = Me.dir.get_child('data').get_child('images');
const USER_IMAGES = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'images']));
var Image = new Lang.Class({
Name: 'DrawOnYourScreenImage',
_init: function(params) {
for (let key in params)
this[key] = params[key];
},
toString: function() {
return this.displayName;
},
toJson: function() {
return {
displayName: this.displayName,
contentType: this.contentType,
_base64: this.base64,
_hash: this.hash
};
},
// only called from menu so file exists
get gicon() {
if (!this._gicon)
this._gicon = new Gio.FileIcon({ file: this.file });
return this._gicon;
},
get bytes() {
if (!this._bytes) {
if (this.file)
this._bytes = this.file.load_bytes(null)[0];
else
this._bytes = new GLib.Bytes(GLib.base64_decode(this._base64));
}
return this._bytes;
},
get base64() {
if (!this._base64)
this._base64 = GLib.base64_encode(this.bytes.get_data());
return this._base64;
},
get hash() {
if (!this._hash)
this._hash = this.bytes.hash();
return this._hash;
},
get pixbuf() {
if (!this._pixbuf) {
let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes);
this._pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
stream.close(null);
}
return this._pixbuf;
},
getPixbufAtScale: function(width, height) {
let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes);
let pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, width, height, true, null);
stream.close(null);
return pixbuf;
},
setCairoSource: function(cr, x, y, width, height, preserveAspectRatio) {
let pixbuf = preserveAspectRatio ? this.getPixbufAtScale(width, height)
: this.pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR);
Gdk.cairo_set_source_pixbuf(cr, pixbuf, x, y);
}
});
var getImages = function() {
let images = [];
[EXAMPLE_IMAGES, USER_IMAGES].forEach(directory => {
let enumerator;
try {
enumerator = directory.enumerate_children('standard::display-name,standard::content-type', Gio.FileQueryInfoFlags.NONE, null);
} catch(e) {
return;
}
let fileInfo = enumerator.next_file(null);
while (fileInfo) {
if (fileInfo.get_content_type().indexOf('image') == 0)
images.push(new Image({ file: enumerator.get_child(fileInfo), contentType: fileInfo.get_content_type(), displayName: fileInfo.get_display_name() }));
fileInfo = enumerator.next_file(null);
}
enumerator.close(null);
});
images.sort((a, b) => {
return b.displayName < a.displayName;
});
return images;
};

View File

@ -179,6 +179,9 @@ msgstr ""
msgid "Select polyline" msgid "Select polyline"
msgstr "" msgstr ""
msgid "Select image"
msgstr ""
msgid "Select text" msgid "Select text"
msgstr "" msgstr ""
@ -227,6 +230,9 @@ msgstr ""
msgid "Toggle text alignment" msgid "Toggle text alignment"
msgstr "" msgstr ""
msgid "Change image file"
msgstr ""
msgid "Hide panel and dock" msgid "Hide panel and dock"
msgstr "" msgstr ""
@ -316,6 +322,9 @@ msgstr ""
msgid "Polyline" msgid "Polyline"
msgstr "" msgstr ""
msgid "Image"
msgstr ""
msgid "Move" msgid "Move"
msgstr "" msgstr ""

42
menu.js
View File

@ -190,6 +190,18 @@ var DrawingMenu = new Lang.Class({
fontSection.itemActivated = () => {}; fontSection.itemActivated = () => {};
this.fontSection = fontSection; this.fontSection = fontSection;
let imageSection = new PopupMenu.PopupMenuSection();
let images = this.area.getImages();
if (images.length) {
if (this.area.currentImage > images.length - 1)
this.area.currentImage = images.length - 1;
this._addSubMenuItem(imageSection, null, images, this.area, 'currentImage');
}
this._addSeparator(imageSection);
this.menu.addMenuItem(imageSection);
imageSection.itemActivated = () => {};
this.imageSection = imageSection;
let manager = Extension.manager; let manager = Extension.manager;
this._addSimpleSwitchItem(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager)); this._addSimpleSwitchItem(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager));
this._addSimpleSwitchItem(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area)); this._addSimpleSwitchItem(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area));
@ -235,19 +247,13 @@ var DrawingMenu = new Lang.Class({
}, },
_updateSectionVisibility: function() { _updateSectionVisibility: function() {
if (this.area.currentTool == Area.Tools.TEXT) { let [isText, isImage] = [this.area.currentTool == Area.Tools.TEXT, this.area.currentTool == Area.Tools.IMAGE];
this.lineSection.actor.hide(); this.lineSection.actor.visible = !isText && !isImage;
this.fontSection.actor.show(); this.fontSection.actor.visible = isText;
this.colorItem.setSensitive(true); this.imageSection.actor.visible = isImage;
this.fillItem.setSensitive(false); this.colorItem.setSensitive(!isImage);
this.fillSection.setSensitive(false); this.fillItem.setSensitive(!isText && !isImage);
} else { this.fillSection.setSensitive(!isText && !isImage);
this.lineSection.actor.show();
this.fontSection.actor.hide();
this.colorItem.setSensitive(true);
this.fillItem.setSensitive(true);
this.fillSection.setSensitive(true);
}
if (this.area.fill) if (this.area.fill)
this.fillSection.actor.show(); this.fillSection.actor.show();
@ -320,7 +326,9 @@ var DrawingMenu = new Lang.Class({
}, },
_addSubMenuItem: function(menu, icon, obj, target, targetProperty, callback) { _addSubMenuItem: function(menu, icon, obj, target, targetProperty, callback) {
let item = new PopupMenu.PopupSubMenuMenuItem(_(obj[target[targetProperty]]), icon ? true : false); if (targetProperty == 'currentImage')
icon = obj[target[targetProperty]].gicon;
let item = new PopupMenu.PopupSubMenuMenuItem(_(String(obj[target[targetProperty]])), icon ? true : false);
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon)) if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon))
item.icon.set_gicon(icon); item.icon.set_gicon(icon);
else if (icon) else if (icon)
@ -340,12 +348,14 @@ var DrawingMenu = new Lang.Class({
else if (targetProperty == 'currentFontStyle') else if (targetProperty == 'currentFontStyle')
text = `<span font_style="${obj[i].toLowerCase()}">${_(obj[i])}</span>`; text = `<span font_style="${obj[i].toLowerCase()}">${_(obj[i])}</span>`;
else else
text = _(obj[i]); text = _(String(obj[i]));
let iCaptured = Number(i); let iCaptured = Number(i);
let subItem = item.menu.addAction(text, () => { let subItem = item.menu.addAction(text, () => {
item.label.set_text(_(obj[iCaptured])); item.label.set_text(_(String(obj[iCaptured])));
target[targetProperty] = iCaptured; target[targetProperty] = iCaptured;
if (targetProperty == 'currentImage')
item.icon.set_gicon(obj[iCaptured].gicon);
if (callback) if (callback)
callback(); callback();
}); });

View File

@ -54,6 +54,7 @@ var INTERNAL_KEYBINDINGS = {
'select-polygon-shape': "Select polygon", 'select-polygon-shape': "Select polygon",
'select-polyline-shape': "Select polyline", 'select-polyline-shape': "Select polyline",
'select-text-shape': "Select text", 'select-text-shape': "Select text",
'select-image-shape': "Select image",
'select-move-tool': "Select move", 'select-move-tool': "Select move",
'select-resize-tool': "Select resize", 'select-resize-tool': "Select resize",
'select-mirror-tool': "Select mirror", 'select-mirror-tool': "Select mirror",
@ -73,6 +74,7 @@ var INTERNAL_KEYBINDINGS = {
'toggle-font-weight': "Change font weight", 'toggle-font-weight': "Change font weight",
'toggle-font-style': "Change font style", 'toggle-font-style': "Change font style",
'toggle-text-alignment': "Toggle text alignment", 'toggle-text-alignment': "Toggle text alignment",
'toggle-image-file': "Change image file",
'-separator-5': '', '-separator-5': '',
'toggle-panel-and-dock-visibility': "Hide panel and dock", 'toggle-panel-and-dock-visibility': "Hide panel and dock",
'toggle-background': "Add a drawing background", 'toggle-background': "Add a drawing background",

Binary file not shown.

View File

@ -106,6 +106,11 @@
<summary>select text</summary> <summary>select text</summary>
<description>select text</description> <description>select text</description>
</key> </key>
<key type="as" name="select-image-shape">
<default>["&lt;Primary&gt;i"]</default>
<summary>select image</summary>
<description>select image</description>
</key>
<key type="as" name="select-none-shape"> <key type="as" name="select-none-shape">
<default>["&lt;Primary&gt;p"]</default> <default>["&lt;Primary&gt;p"]</default>
<summary>unselect shape (free drawing)</summary> <summary>unselect shape (free drawing)</summary>
@ -227,7 +232,7 @@
<description>toggle font weight</description> <description>toggle font weight</description>
</key> </key>
<key type="as" name="toggle-font-style"> <key type="as" name="toggle-font-style">
<default>["&lt;Primary&gt;i"]</default> <default>["&lt;Primary&gt;&lt;Shift&gt;w"]</default>
<summary>toggle font style</summary> <summary>toggle font style</summary>
<description>toggle font style</description> <description>toggle font style</description>
</key> </key>
@ -236,6 +241,11 @@
<summary>toggle text alignment</summary> <summary>toggle text alignment</summary>
<description>toggle text alignment</description> <description>toggle text alignment</description>
</key> </key>
<key type="as" name="toggle-image-file">
<default>["&lt;Primary&gt;&lt;Shift&gt;i"]</default>
<summary>toggle image file</summary>
<description>toggle image file</description>
</key>
<key type="as" name="open-user-stylesheet"> <key type="as" name="open-user-stylesheet">
<default>["&lt;Primary&gt;o"]</default> <default>["&lt;Primary&gt;o"]</default>
<summary>open user stylesheet to edit style</summary> <summary>open user stylesheet to edit style</summary>