From b45c86a5afcedac62b2547001a2aaa9d8dbc4815 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 1 Jun 2020 17:09:44 +0200 Subject: [PATCH 01/67] escape empty media-keys settings in the helper close #28 --- draw.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/draw.js b/draw.js index cb542ac..1dae585 100644 --- a/draw.js +++ b/draw.js @@ -1136,6 +1136,8 @@ var DrawingHelper = new Lang.Class({ if (!mediaKeysSettings.settings_schema.has_key(settingKey)) continue; let shortcut = GS_VERSION < '3.33.0' ? mediaKeysSettings.get_string(settingKey) : mediaKeysSettings.get_strv(settingKey)[0]; + if (!shortcut) + continue; let [keyval, mods] = Gtk.accelerator_parse(shortcut); let hbox = new St.BoxLayout({ vertical: false }); hbox.add_child(new St.Label({ text: _(MEDIA_KEYS_KEYS[settingKey]) })); From a4456762b5ac23e40d07377317f03804b6bb3ee8 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 1 Jun 2020 17:37:08 +0200 Subject: [PATCH 02/67] not to populate helper widget before the first showing Does not fix anything, just a marginal gain. --- draw.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/draw.js b/draw.js index 1dae585..679e04e 100644 --- a/draw.js +++ b/draw.js @@ -1079,6 +1079,9 @@ var DrawingHelper = new Lang.Class({ this.parent(params); this.monitor = monitor; this.hide(); + }, + + _populate: function() { this.vbox = new St.BoxLayout({ vertical: true }); this.add_actor(this.vbox); this.vbox.add_child(new St.Label({ text: _("Global") })); @@ -1147,6 +1150,9 @@ var DrawingHelper = new Lang.Class({ }, showHelp: function() { + if (!this.vbox) + this._populate(); + this.opacity = 0; this.show(); From 11a00a25d04e677745974df44ebeb4485849a12c Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 1 Jun 2020 19:28:06 +0200 Subject: [PATCH 03/67] Fix font-color label color Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost. close #31 --- draw.js | 33 ++++++++++++++++++--------------- extension.js | 12 +++++++++++- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/draw.js b/draw.js index 679e04e..330ffce 100644 --- a/draw.js +++ b/draw.js @@ -108,7 +108,7 @@ const getJsonFiles = function() { var DrawingArea = new Lang.Class({ Name: 'DrawOnYourScreenDrawingArea', Extends: St.DrawingArea, - Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] }, + Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] }, 'stop-drawing': {} }, _init: function(params, monitor, helper, loadPersistent) { @@ -378,7 +378,7 @@ var DrawingArea = new Lang.Class({ if (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.DRAWING) { this.currentElement.state = TextState.WRITING; this.currentElement.text = ''; - this.emit('show-osd', null, _("Type your text\nand press Enter"), -1); + this.emit('show-osd', null, _("Type your text\nand press Enter"), "", -1); this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); @@ -525,38 +525,39 @@ var DrawingArea = new Lang.Class({ this.currentElement.color = this.currentColor.to_string(); this._redisplay(); } - this.emit('show-osd', null, `${this.currentColor.to_string()}`, -1); + // Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost. + this.emit('show-osd', null, this.currentColor.to_string(), this.currentColor.to_string().slice(0, 7), -1); }, selectShape: function(shape) { this.currentShape = shape; - this.emit('show-osd', null, _(ShapeNames[shape]), -1); + this.emit('show-osd', null, _(ShapeNames[shape]), "", -1); this.updatePointerCursor(); }, toggleFill: function() { this.fill = !this.fill; - this.emit('show-osd', null, this.fill ? _("Fill") : _("Stroke"), -1); + this.emit('show-osd', null, this.fill ? _("Fill") : _("Stroke"), "", -1); }, toggleDash: function() { this.dashedLine = !this.dashedLine; - this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), -1); + this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), "", -1); }, incrementLineWidth: function(increment) { this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0); - this.emit('show-osd', null, this.currentLineWidth + " " + _("px"), 2 * this.currentLineWidth); + this.emit('show-osd', null, this.currentLineWidth + " " + _("px"), "", 2 * this.currentLineWidth); }, toggleLineJoin: function() { this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1; - this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), -1); + this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), "", -1); }, toggleLineCap: function() { this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1; - this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), -1); + this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), "", -1); }, toggleFontWeight: function() { @@ -565,7 +566,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.weight = this.currentFontWeight; this._redisplay(); } - this.emit('show-osd', null, `${_(FontWeightNames[this.currentFontWeight])}`, -1); + this.emit('show-osd', null, `${_(FontWeightNames[this.currentFontWeight])}`, "", -1); }, toggleFontStyle: function() { @@ -574,7 +575,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.style = this.currentFontStyle; this._redisplay(); } - this.emit('show-osd', null, `${_(FontStyleNames[this.currentFontStyle])}`, -1); + this.emit('show-osd', null, `${_(FontStyleNames[this.currentFontStyle])}`, "", -1); }, toggleFontFamily: function() { @@ -584,7 +585,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.family = currentFontFamily; this._redisplay(); } - this.emit('show-osd', null, `${_(currentFontFamily)}`, -1); + this.emit('show-osd', null, `${_(currentFontFamily)}`, "", -1); }, toggleHelp: function() { @@ -729,7 +730,7 @@ var DrawingArea = new Lang.Class({ GLib.file_set_contents(path, contents); if (notify) - this.emit('show-osd', 'document-save-symbolic', name, -1); + this.emit('show-osd', 'document-save-symbolic', name, "", -1); if (name != Me.metadata['persistent-file-name']) { this.jsonName = name; this.lastJsonContents = contents; @@ -771,7 +772,7 @@ var DrawingArea = new Lang.Class({ this.elements.push(...JSON.parse(contents).map(object => new DrawingElement(object))); if (notify) - this.emit('show-osd', 'document-open-symbolic', name, -1); + this.emit('show-osd', 'document-open-symbolic', name, "", -1); if (name != Me.metadata['persistent-file-name']) { this.jsonName = name; this.lastJsonContents = contents; @@ -1436,13 +1437,15 @@ const DrawingMenu = new Lang.Class({ Mainloop.timeout_add(0, () => { for (let i = 1; i < this.area.colors.length; i++) { - let text = `${this.area.colors[i].to_string()}`; + let text = this.area.colors[i].to_string(); let iCaptured = i; let colorItem = item.menu.addAction(text, () => { this.area.currentColor = this.area.colors[iCaptured]; item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`); }); colorItem.label.get_clutter_text().set_use_markup(true); + // Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost. + colorItem.label.set_style(`color:${this.area.colors[i].to_string().slice(0, 7)};`); } return GLib.SOURCE_REMOVE; }); diff --git a/extension.js b/extension.js index ef5d9ed..9eef841 100644 --- a/extension.js +++ b/extension.js @@ -339,7 +339,7 @@ var AreaManager = new Lang.Class({ }, // use level -1 to set no level (null) - showOsd: function(emitter, iconName, label, level) { + showOsd: function(emitter, iconName, label, color, level) { if (this.osdDisabled) return; let activeIndex = this.areas.indexOf(this.activeArea); @@ -359,6 +359,16 @@ var AreaManager = new Lang.Class({ Main.osdWindowManager.show(activeIndex, icon || this.enterGicon, label, level, maxLevel); Main.osdWindowManager._osdWindows[activeIndex]._label.get_clutter_text().set_use_markup(true); + if (color) { + Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:${color};`); + Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:${color};`); + let osdColorChangedHandler = Main.osdWindowManager._osdWindows[activeIndex]._label.connect('notify::text', () => { + Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:;`); + Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:;`); + Main.osdWindowManager._osdWindows[activeIndex]._label.disconnect(osdColorChangedHandler); + }); + } + if (level === 0) { Main.osdWindowManager._osdWindows[activeIndex]._label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); // the same label is shared by all GS OSD so the style must be removed after being used From 91475c8b705de028d7783fb169c8e858145c09c6 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 1 Jun 2020 20:28:26 +0200 Subject: [PATCH 04/67] Fix "Hide panel and dock" --- extension.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extension.js b/extension.js index 9eef841..68c4347 100644 --- a/extension.js +++ b/extension.js @@ -262,19 +262,23 @@ var AreaManager = new Lang.Class({ // dash-to-dock let dtdContainers = Main.uiGroup.get_children().filter((actor) => { return actor.name && actor.name == 'dashtodockContainer' && - actor._delegate && + ((actor._delegate && actor._delegate._monitorIndex !== undefined && - actor._delegate._monitorIndex == activeIndex; + actor._delegate._monitorIndex == activeIndex) || + // dtd v68+ + (actor._monitorIndex !== undefined && + actor._monitorIndex == activeIndex)); }); // for simplicity, we assume that main dash-to-panel panel is displayed on primary monitor // and we hide all secondary panels together if the active area is not on the primary let name = activeIndex == Main.layoutManager.primaryIndex ? 'panelBox' : 'dashtopanelSecondaryPanelBox'; let panelBoxes = Main.uiGroup.get_children().filter((actor) => { - return actor.name && actor.name == name; + return actor.name && actor.name == name || + // dtp v37+ + actor.get_children().length && actor.get_children()[0].name && actor.get_children()[0].name == name; }); - let actorToHide = dtdContainers.concat(panelBoxes); this.hiddenList = []; for (let i = 0; i < actorToHide.length; i++) { From 360ac081cef456826534f31d9f3773d8fcbf4ef5 Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 5 Jun 2020 15:58:58 +0200 Subject: [PATCH 05/67] Add grid overlay --- data/default.css | 4 ++ draw.js | 38 ++++++++++++++++++ extension.js | 1 + locale/draw-on-your-screen.pot | 3 ++ prefs.js | 1 + schemas/gschemas.compiled | Bin 3456 -> 3540 bytes ...extensions.draw-on-your-screen.gschema.xml | 9 ++++- 7 files changed, 54 insertions(+), 2 deletions(-) diff --git a/data/default.css b/data/default.css index b5ab3ef..f6015ea 100644 --- a/data/default.css +++ b/data/default.css @@ -46,6 +46,10 @@ -drawing-color8: rgba(130, 130, 130, 0.3); -drawing-color9: rgb(0, 0, 0); -drawing-background-color: #2e3436; + -grid-overlay-gap: 10px; + -grid-overlay-line-width: 0.4px; + -grid-overlay-interline-width: 0.2px; + -grid-overlay-color: gray; /*-drawing-square-area-width: 512px;*/ /*-drawing-square-area-height: 512px;*/ font-family: Cantarell; diff --git a/draw.js b/draw.js index 330ffce..c71b5ac 100644 --- a/draw.js +++ b/draw.js @@ -123,6 +123,7 @@ var DrawingArea = new Lang.Class({ this.currentElement = null; this.currentShape = Shapes.NONE; this.isSquareArea = false; + this.hasGrid = false; this.hasBackground = false; this.textHasCursor = false; this.dashedLine = false; @@ -160,6 +161,10 @@ var DrawingArea = new Lang.Class({ this.fontFamily = font.get_family(); this.currentFontWeight = font.get_weight(); this.currentFontStyle = font.get_style(); + this.gridGap = themeNode.get_length('-grid-overlay-gap') || 10; + this.gridLineWidth = themeNode.get_length('-grid-overlay-line-width') || 0.4; + this.gridInterlineWidth = themeNode.get_length('-grid-overlay-interline-width') || 0.2; + this.gridColor = themeNode.get_color('-grid-overlay-color'); this.squareAreaWidth = themeNode.get_length('-drawing-square-area-width'); this.squareAreaHeight = themeNode.get_length('-drawing-square-area-height'); } catch(e) { @@ -178,6 +183,11 @@ var DrawingArea = new Lang.Class({ this.currentFontWeight = this.currentFontWeight > 500 ? 1 : 0 ; // font style enum order of Cairo and Pango are different this.currentFontStyle = this.currentFontStyle == 2 ? 1 : ( this.currentFontStyle == 1 ? 2 : 0); + + this.gridGap = this.gridGap && this.gridGap >= 1 ? this.gridGap : 10; + this.gridLineWidth = this.gridLineWidth || 0.4; + this.gridInterlineWidth = this.gridInterlineWidth || 0.2; + this.gridColor = this.gridColor && this.gridColor.alpha ? this.gridColor : Clutter.Color.new(127, 127, 127, 255); }, vfunc_repaint: function() { @@ -207,6 +217,26 @@ var DrawingArea = new Lang.Class({ cr.stroke(); } + if (this.isInDrawingMode && this.hasGrid && this.gridGap && this.gridGap >= 1) { + Clutter.cairo_set_source_color(cr, this.gridColor); + + let [gridX, gridY] = [this.gridGap, this.gridGap]; + while (gridX < this.monitor.width) { + cr.setLineWidth((gridX / this.gridGap) % 5 ? this.gridInterlineWidth : this.gridLineWidth); + cr.moveTo(gridX, 0); + cr.lineTo(gridX, this.monitor.height); + gridX += this.gridGap; + cr.stroke(); + } + while (gridY < this.monitor.height) { + cr.setLineWidth((gridY / this.gridGap) % 5 ? this.gridInterlineWidth : this.gridLineWidth); + cr.moveTo(0, gridY); + cr.lineTo(this.monitor.width, gridY); + gridY += this.gridGap; + cr.stroke(); + } + } + cr.$dispose(); }, @@ -500,6 +530,11 @@ var DrawingArea = new Lang.Class({ this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null); }, + toggleGrid: function() { + this.hasGrid = !this.hasGrid; + this._redisplay(); + }, + toggleSquareArea: function() { this.isSquareArea = !this.isSquareArea; if (this.isSquareArea) { @@ -596,6 +631,7 @@ var DrawingArea = new Lang.Class({ }, enterDrawingMode: function() { + this.isInDrawingMode = true; this.stageKeyPressedHandler = global.stage.connect('key-press-event', this._onStageKeyPressed.bind(this)); this.stageKeyReleasedHandler = global.stage.connect('key-release-event', this._onStageKeyReleased.bind(this)); this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this)); @@ -607,6 +643,7 @@ var DrawingArea = new Lang.Class({ }, leaveDrawingMode: function(save) { + this.isInDrawingMode = false; if (this.stageKeyPressedHandler) { global.stage.disconnect(this.stageKeyPressedHandler); this.stageKeyPressedHandler = null; @@ -1308,6 +1345,7 @@ const DrawingMenu = new Lang.Class({ let manager = Extension.manager; this._addSwitchItemWithCallback(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager)); this._addSwitchItemWithCallback(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area)); + this._addSwitchItemWithCallback(this.menu, _("Add a grid overlay"), this.area.hasGrid, this.area.toggleGrid.bind(this.area)); this._addSwitchItemWithCallback(this.menu, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area)); this._addSeparator(this.menu); diff --git a/extension.js b/extension.js index 68c4347..8fa8ad2 100644 --- a/extension.js +++ b/extension.js @@ -176,6 +176,7 @@ var AreaManager = new Lang.Class({ 'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea), 'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea), 'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea), + 'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea), 'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea), 'increment-line-width': () => this.activeArea.incrementLineWidth(1), 'decrement-line-width': () => this.activeArea.incrementLineWidth(-1), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 2df55f0..676752b 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -199,6 +199,9 @@ msgstr "" msgid "Add a drawing background" msgstr "" +msgid "Add a grid overlay" +msgstr "" + msgid "Square drawing area" msgstr "" diff --git a/prefs.js b/prefs.js index 3971165..114c7f3 100644 --- a/prefs.js +++ b/prefs.js @@ -65,6 +65,7 @@ var INTERNAL_KEYBINDINGS = { '-separator-4': '', 'toggle-panel-and-dock-visibility': "Hide panel and dock", 'toggle-background': "Add a drawing background", + 'toggle-grid': "Add a grid overlay", 'toggle-square-area': "Square drawing area", '-separator-5': '', 'open-previous-json': "Open previous drawing", diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index eebc890de348b295b81de90a8af5f07457f944f1..1cd66c59bb02fdc2bc1e162edaad257b4dc06f6c 100644 GIT binary patch literal 3540 zcmZ`+U2GIp6dsi#|CC?bQnVn#wmQ3(QYfZY&Qe{qE9u1D%61oc*7R6Ana`41k2VgzP@`!zD>UskKHn{bL3>u4 z0)G#fhJ%r@F1`bSPyIfnt>MJ zVL%61H&N62(LCnjysXrQv?=g4;JvpG+@PI$DeQlN=gvkQ&cPAdsTaUr2VM=lak}Ly z?bJ=MZv*!Ni^m>orJb5_a^O5L@aWEV+Nl@8ei(cV82qQLk#=hOPk^rghjusqLOb~+F9c4WU+dCNJszjkc52#l z;8Eb_k4JiGr)Hhw;1fXK@rxhQPEG#_@D-rEx2U<4@ZW&lyZ;H=sq0~{!v<>yo*emY zigxOSu=jv-z|Uva$oZjWJtN>Zfg2Z+uhO5I{>Q=RfTvU(YT75k)4=2_hvxGvQP;qJ z9lQz~yIc8F(?0?30S>)B@jl~Iv(6m&E#PV8Pc8F;F9GRpKPh0XX(~?*!ve zFJoTtEkJAd=@#wOT(4Rju64i;6`z{%JHUOwPGzU2JqI2KijVI*&p6av-&5d^fga^g zP5(*oG_XtAscF9nuEn9=t?bmaH-WbSll|68#;4}E`oIu9HDJ(s}jv%auJCeS_nF`TR| zbU$Yl1RU|{+~Zk!!yE1LkRoCD&4HVxOTX&`dcTpk?9ocUe&y%-rfyokk+y{?Jre2i zcMFCiY~66+;ARH(A2#O${2y`{oimh0Bvj(&pSPwJ<_XBtpkoPuQ$3p<`TC_0CU+K=h2aKyl-kWOp zSoBQ>JPfcFe+{!-x$Ka@xzT4`uXbnmUHL6 z(eti1^Bk$T*hbq;6VoJvmkwaYwOy|jMa1iXV{24ZtG1}VV)t0KUFmPAdW2{AB04gi z+wGvMb8ivHsY{^y&P=;eMv09dc{fIRHnKKy2|3OEquxd`J4Io xyzs*3YQOucFUhhCo*1&+V$_{t@#H<&?G8I=-^^L9PsQ_NKd~{D#vx17{sX$BGkE|2 literal 3456 zcmZu!U2GIp7#)$yKP~@lsR}5t1!k903dKMbE&rI55KNz>vpaWpr=6WyW@h^b5~3zz z&;)$3F%lsrf(Z#tRDwn$ycjf+;DbuyOG%882MG^EF$VFRyLWeH*WKjwoSymSyWiZo z_s+dLUNSt(Z~{&J8o|9q9h;=QN5GROMmxy77T0!a$H8NDnpU?!(;nh?WEKo$HBGmBz$5iunc%g;b}0j99RLYREU9zR$vvd8d#%n z;hX8N>Co(xdTmfU5B?mu@ZH;u^r`2=zXARY82G2Xg+BFM`1irn@NkQpJ~%?3dNTY5 z@KT_z?W5`RsTacU1n&aQees}yJ~i|7fdz0aIPnR6YWlC zb7Yu4^-TCTz&`?K`YxsEQ_q3_JNR$l`Qblq(5IdSe=1%|47l@Y{8jqY^gF?OffrOf zHT^#DF`!%d)bvk+$AM%_v@s7g>%RuR4rt9kKcG)N8~z0N9WA;4MI5%YlpZso9@BU<25t@>4T^9()qmt$b?wXTVp0=B|+r=Aq`ex(U7w^sn6^ zeQNg4eQ^DB#JzHK2Inbt9sEY{9w0b;y_L0#09C=F+ETp1a^lGc@hxudmwlsaby`xD~MX9ekEPHT!cX zSOD+6b@&c_YB`?4ZvwxZTPpKYGyfR)W8g~KTF7{6#$N&d0Nl$hk@3`wp8)>}9NpdW zE90qIPXiX0)j<8|GjbeKGtYD2ZNS~1PV8emHT$_2YyoeaU3HT_^%D3e!DoTi+li_4 zso8Ir!QTR<^^=bZ~_^l z4_RiAMTT^_W)52wYD$E#FAm!sG+t7fZIfdEw}JPYhaa!PonJH}X6^Mn)X$c845H6PD`d zTsO$-w&4dln&uNSOSxWwXRxuvGS>CY>egg?s%FP^O67+4=PREp<=U>-7Rir(YYU^> zwAoFvDd8Eu(D~`IoDA#PY6o4L_ZRZQ>k=3Nvh-qC$&~9@G=Al}k#}AA-?~y>RjRzF zy(+#vjBhW+s|xu2G=)D9xOv$O{-B@LYYwZdtkbMf=X!N}w9fTmyX#BsM(gaTsiZ{jor-z<%JyHF< zsxG{q{e@)U8L2>PkrkSFn*nBECSG;;K4ipuiP)rCc}(%*@)ouuRGwPvZDc@|^M>Pz z%kLuDgsLI!Vzo{iIm;di^QSB8Ok1{H@tj80b>D)+}gWnFp05w>nPSh(HPfIevX zR?@PqV5Hc5SZ}uSKG}D2h!+FP`7IR(I|kr5g8ebS$^5<-&k@|$V|y*n1K6%=%>cG( zav#q#!xG>L09zA`3GTDPGsQgU`2f#FJX7$Tz%v83Rq{;2vj(=D@_expTn{V diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml index 8890675..1454195 100644 --- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml +++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml @@ -53,8 +53,13 @@ ["<Primary>b"] - toggle background - toggle background + toggle drawing background + toggle drawing background + + + ["<Primary>g"] + toggle grid overlay + toggle grid overlay ["<Primary>h"] From c5721f9752a75e6e80ac1704622e130b0686fa72 Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 5 Jun 2020 22:45:11 +0200 Subject: [PATCH 06/67] no separator on small monitor --- draw.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/draw.js b/draw.js index c71b5ac..5222e12 100644 --- a/draw.js +++ b/draw.js @@ -1239,6 +1239,7 @@ const DrawingMenu = new Lang.Class({ this.menu.actor.add_style_class_name('background-menu draw-on-your-screen-menu'); this.menu.actor.set_style('max-height:' + monitor.height + 'px;'); this.menu.actor.hide(); + this.hasSeparators = monitor.height >= 750; // do not close the menu on item activated this.menu.itemActivated = () => {}; @@ -1597,9 +1598,11 @@ const DrawingMenu = new Lang.Class({ }, _addSeparator: function(menu) { - let separatorItem = new PopupMenu.PopupSeparatorMenuItem(' '); - getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-separator-item'); - menu.addMenuItem(separatorItem); + if (this.hasSeparators) { + let separatorItem = new PopupMenu.PopupSeparatorMenuItem(' '); + getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-separator-item'); + menu.addMenuItem(separatorItem); + } } }); From 0249d12694459a857f477f63c15491d661224b6c Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 5 Jun 2020 22:48:32 +0200 Subject: [PATCH 07/67] css, minor --- data/default.css | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/data/default.css b/data/default.css index f6015ea..67f91a5 100644 --- a/data/default.css +++ b/data/default.css @@ -36,23 +36,27 @@ -drawing-dash-array-on: 5px; -drawing-dash-array-off: 15px; -drawing-dash-offset: 0px; - -drawing-color1: HotPink; - -drawing-color2: Cyan; - -drawing-color3: yellow; - -drawing-color4: Orangered; - -drawing-color5: Chartreuse; - -drawing-color6: DarkViolet; - -drawing-color7: #ffffff; - -drawing-color8: rgba(130, 130, 130, 0.3); - -drawing-color9: rgb(0, 0, 0); -drawing-background-color: #2e3436; -grid-overlay-gap: 10px; -grid-overlay-line-width: 0.4px; -grid-overlay-interline-width: 0.2px; - -grid-overlay-color: gray; + -grid-overlay-color: Gray; /*-drawing-square-area-width: 512px;*/ /*-drawing-square-area-height: 512px;*/ font-family: Cantarell; font-weight: normal; font-style: normal; } + +/* Palette */ +.draw-on-your-screen { + -drawing-color1: HotPink; + -drawing-color2: Cyan; + -drawing-color3: yellow; + -drawing-color4: Orangered; + -drawing-color5: Chartreuse; + -drawing-color6: DarkViolet; + -drawing-color7: White; + -drawing-color8: Gray; + -drawing-color9: Black; +} From c087e359d9fd9158b31387d1a2adb0d347ee3279 Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 5 Jun 2020 22:52:02 +0200 Subject: [PATCH 08/67] hide help on `Escape` key pressed Press the key again to leave drawing mode. --- draw.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/draw.js b/draw.js index 5222e12..8b6c289 100644 --- a/draw.js +++ b/draw.js @@ -297,7 +297,10 @@ var DrawingArea = new Lang.Class({ _onKeyPressed: function(actor, event) { if (event.get_key_symbol() == Clutter.KEY_Escape) { - this.emit('stop-drawing'); + if (this.helper.visible) + this.helper.hideHelp(); + else + this.emit('stop-drawing'); return Clutter.EVENT_STOP; } else if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { From 755b3d79c9cfe8e7ab96d221151fef1c40c131d0 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 6 Jun 2020 00:13:21 +0200 Subject: [PATCH 09/67] text: start a new line with Enter key Finish writing with click or `Escape` key. close #30 --- draw.js | 61 +++++++++++++++++++++------------- locale/draw-on-your-screen.pot | 2 +- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/draw.js b/draw.js index 8b6c289..7fbf66b 100644 --- a/draw.js +++ b/draw.js @@ -248,13 +248,13 @@ var DrawingArea = new Lang.Class({ let [x, y] = event.get_coords(); let shiftPressed = event.has_shift_modifier(); - // stop writing - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING ) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { + // finish writing this._stopWriting(); } - // hide helper if (this.helper.visible) { + // hide helper this.helper.hideHelp(); return Clutter.EVENT_STOP; } @@ -296,31 +296,27 @@ var DrawingArea = new Lang.Class({ }, _onKeyPressed: function(actor, event) { - if (event.get_key_symbol() == Clutter.KEY_Escape) { - if (this.helper.visible) - this.helper.hideHelp(); - else - this.emit('stop-drawing'); - return Clutter.EVENT_STOP; - - } else if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { - if (event.get_key_symbol() == Clutter.KEY_BackSpace) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + // finish writing + this._stopWriting(); + } else if (event.get_key_symbol() == Clutter.KEY_BackSpace) { this.currentElement.text = this.currentElement.text.slice(0, -1); this._updateTextCursorTimeout(); } else if (event.has_control_modifier() && event.get_key_symbol() == 118) { - // Ctrl + V + // Ctrl + V St.Clipboard.get_default().get_text(St.ClipboardType.CLIPBOARD, (clipBoard, clipText) => { this.currentElement.text += clipText; this._updateTextCursorTimeout(); this._redisplay(); }); - return Clutter.EVENT_STOP; } else if (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421) { - // stop writing - // Clutter.KEY_Return is "Enter" and 65421 is KP_Enter - this._stopWriting(); + // Clutter.KEY_Return is "Enter" and 65421 is KP_Enter + // start a new line + let startNewLine = true; + this._stopWriting(startNewLine); } else if (event.has_control_modifier()){ - // it's a shortcut, do not write text + // it is a shortcut, do not write text return Clutter.EVENT_PROPAGATE; } else { let unicode = event.get_key_unicode(); @@ -329,6 +325,13 @@ var DrawingArea = new Lang.Class({ } this._redisplay(); return Clutter.EVENT_STOP; + + } else if (event.get_key_symbol() == Clutter.KEY_Escape) { + if (this.helper.visible) + this.helper.hideHelp(); + else + this.emit('stop-drawing'); + return Clutter.EVENT_STOP; } else { return Clutter.EVENT_PROPAGATE; @@ -407,11 +410,11 @@ var DrawingArea = new Lang.Class({ (this.currentShape == Shapes.NONE || Math.hypot(this.currentElement.points[1][0] - this.currentElement.points[0][0], this.currentElement.points[1][1] - this.currentElement.points[0][1]) > 3)) { - // start writing if (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.DRAWING) { + // start writing this.currentElement.state = TextState.WRITING; this.currentElement.text = ''; - this.emit('show-osd', null, _("Type your text\nand press Enter"), "", -1); + this.emit('show-osd', null, _("Type your text\nand press Escape"), "", -1); this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); @@ -445,11 +448,23 @@ var DrawingArea = new Lang.Class({ this.updatePointerCursor(controlPressed); }, - _stopWriting: function() { + _stopWriting: function(startNewLine) { if (this.currentElement.text.length > 0) this.elements.push(this.currentElement); - this.currentElement = null; - this._stopTextCursorTimeout(); + if (startNewLine && this.currentElement.points.length == 2) { + // copy object, the original keep existing in this.elements + this.currentElement = Object.create(this.currentElement); + let height = Math.abs(this.currentElement.points[1][1] - this.currentElement.points[0][1]); + // define a new 'points' array, the original keep existing in this.elements + this.currentElement.points = [ + [this.currentElement.points[0][0], this.currentElement.points[0][1] + height], + [this.currentElement.points[1][0], this.currentElement.points[1][1] + height] + ]; + this.currentElement.text = ""; + } else { + this.currentElement = null; + this._stopTextCursorTimeout(); + } this._redisplay(); }, diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 676752b..8452fe8 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -72,7 +72,7 @@ msgstr "" msgid "" "Type your text\n" -"and press Enter" +"and press Escape" msgstr "" msgid "Screenshot" From b69c4ccf97025ed9cd18025ca4e4cb4546d660f0 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 7 Jun 2020 18:57:11 +0200 Subject: [PATCH 10/67] new polygon shape Mark vertices with `Enter` and finish drawing by releasing clic, like other shapes. --- draw.js | 107 +++++++++++++++--- extension.js | 1 + locale/draw-on-your-screen.pot | 11 ++ prefs.js | 1 + schemas/gschemas.compiled | Bin 3540 -> 3588 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 + 6 files changed, 110 insertions(+), 15 deletions(-) diff --git a/draw.js b/draw.js index 7fbf66b..27f3d6d 100644 --- a/draw.js +++ b/draw.js @@ -56,9 +56,9 @@ const LINECAP_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child( const DASHED_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('dashed-line-symbolic.svg').get_path(); const FULL_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('full-line-symbolic.svg').get_path(); -var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4 }; +var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5 }; const TextState = { DRAWING: 0, WRITING: 1 }; -const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text" }; +const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; const FontWeightNames = { 0: 'Normal', 1: 'Bold' }; @@ -325,7 +325,16 @@ var DrawingArea = new Lang.Class({ } this._redisplay(); return Clutter.EVENT_STOP; - + + } else if (this.currentElement && this.currentElement.shape == Shapes.POLYGON && + (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421)) { + // copy last point + let lastPoint = this.currentElement.points[this.currentElement.points.length - 1]; + let secondToLastPoint = this.currentElement.points[this.currentElement.points.length - 2]; + if (!getNearness(secondToLastPoint, lastPoint, 3)) + this.currentElement.points.push([lastPoint[0], lastPoint[1]]); + return Clutter.EVENT_STOP; + } else if (event.get_key_symbol() == Clutter.KEY_Escape) { if (this.helper.visible) this.helper.hideHelp(); @@ -382,6 +391,11 @@ var DrawingArea = new Lang.Class({ this.currentElement.state = TextState.DRAWING; } + if (this.currentShape == Shapes.POLYGON) { + this.currentElement.points.push([startX, startY]); + this.emit('show-osd', null, _("Press Enter\nto mark vertices"), "", -1); + } + this.motionHandler = this.connect('motion-event', (actor, event) => { if (this.spaceKeyPressed) return; @@ -405,11 +419,19 @@ var DrawingArea = new Lang.Class({ this.buttonReleasedHandler = null; } + // skip when a polygon has not at least 3 points + if (this.currentElement && this.currentElement.shape == Shapes.POLYGON && this.currentElement.points.length < 3) + this.currentElement = null; + // skip when the size is too small to be visible (3px) (except for free drawing) - if (this.currentElement && this.currentElement.points.length >= 2 && - (this.currentShape == Shapes.NONE || - Math.hypot(this.currentElement.points[1][0] - this.currentElement.points[0][0], this.currentElement.points[1][1] - this.currentElement.points[0][1]) > 3)) { - + if (this.currentElement && this.currentElement.shape != Shapes.NONE && this.currentElement.points.length >= 2) { + let lastPoint = this.currentElement.points[this.currentElement.points.length - 1]; + let secondToLastPoint = this.currentElement.points[this.currentElement.points.length - 2]; + if (getNearness(secondToLastPoint, lastPoint, 3)) + this.currentElement.points.pop(); + } + + if (this.currentElement && this.currentElement.points.length >= 2) { if (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.DRAWING) { // start writing this.currentElement.state = TextState.WRITING; @@ -436,11 +458,15 @@ var DrawingArea = new Lang.Class({ if (this.currentElement.shape == Shapes.NONE) this.currentElement.addPoint(x, y, controlPressed); else if ((this.currentElement.shape == Shapes.RECTANGLE || this.currentElement.shape == Shapes.TEXT) && (controlPressed || this.currentElement.transform.active)) - this.currentElement.transformRectangle(x, y); + this.currentElement.transformPolygon(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 if (this.currentElement.shape == Shapes.POLYGON && (controlPressed || this.currentElement.transform.active)) + this.currentElement.transformPolygon(x, y); + else if (this.currentElement.shape == Shapes.POLYGON) + this.currentElement.points[this.currentElement.points.length - 1] = [x, y]; else this.currentElement.points[1] = [x, y]; @@ -928,6 +954,7 @@ const DrawingElement = new Lang.Class({ 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++) { @@ -946,6 +973,16 @@ const DrawingElement = new Lang.Class({ 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.POLYGON && points.length >= 2) { + this.rotate(cr, trans.angle, trans.center[0], trans.center[1]); + cr.moveTo(points[0][0], points[0][1]); + for (let j = 1; j < points.length; j++) { + cr.lineTo(points[j][0], points[j][1]); + } + if (shape == Shapes.POLYGON) + cr.closePath(); + 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]); @@ -982,9 +1019,8 @@ const DrawingElement = new Lang.Class({ } else if (this.shape == Shapes.NONE || this.shape == Shapes.LINE) { row += ``; } else if (this.shape == Shapes.ELLIPSE && points.length == 2 && this.transform.ratio != 1) { @@ -1006,6 +1042,17 @@ const DrawingElement = new Lang.Class({ row += ``; + } else if (this.shape == Shapes.POLYGON && points.length >= 3) { + 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 += ``; + } else if (this.shape == Shapes.TEXT && points.length == 2) { let transAttribute = ""; if (this.transform.angle != 0) { @@ -1060,14 +1107,13 @@ const DrawingElement = new Lang.Class({ cr.translate(-x, -y); }, - transformRectangle: function(x, y) { + transformPolygon: 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.center = points.length >= 3 ? getCentroid(points) : getNaiveCenter(points); + this.transform.angle = getAngle(this.transform.center[0], this.transform.center[1], points[points.length - 1][0], points[points.length - 1][1], x, y); this.transform.active = true; }, @@ -1096,9 +1142,36 @@ const DrawingElement = new Lang.Class({ this.points[2] = this.points[1]; this.points[1] = [x, y]; this.transform.active = true; - }, + } }); +const getNearness = function(pointA, pointB, distance) { + return Math.hypot(pointB[0] - pointA[0], pointB[1] - pointA[1]) < distance; +}; + +// mean of the vertices, ok for regular polygons +const getNaiveCenter = function(points) { + return points.reduce((accumulator, point) => accumulator = [accumulator[0] + point[0], accumulator[1] + point[1]]) + .map(coord => coord / points.length); +}; + +// https://en.wikipedia.org/wiki/Centroid#Of_a_polygon +const getCentroid = function(points) { + let n = points.length; + points.push(points[0]); + + let [sA, sX, sY] = [0, 0, 0]; + for (let i = 0; i <= n-1; i++) { + let a = points[i][0]*points[i+1][1] - points[i+1][0]*points[i][1]; + sA += a; + sX += (points[i][0] + points[i+1][0]) * a; + sY += (points[i][1] + points[i+1][1]) * a; + } + + points.pop(); + return [sX / (3 * sA), sY / (3 * sA)]; +}; + const getAngle = function(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) @@ -1477,6 +1550,10 @@ const DrawingMenu = new Lang.Class({ }); subItem.label.get_clutter_text().set_use_markup(true); + + // change the display order of shapes + if (obj == ShapeNames && i == Shapes.POLYGON) + item.menu.moveMenuItem(subItem, 4); } return GLib.SOURCE_REMOVE; }); diff --git a/extension.js b/extension.js index 8fa8ad2..bc628a8 100644 --- a/extension.js +++ b/extension.js @@ -191,6 +191,7 @@ var AreaManager = new Lang.Class({ 'select-ellipse-shape': () => this.activeArea.selectShape(Draw.Shapes.ELLIPSE), 'select-rectangle-shape': () => this.activeArea.selectShape(Draw.Shapes.RECTANGLE), 'select-text-shape': () => this.activeArea.selectShape(Draw.Shapes.TEXT), + 'select-polygon-shape': () => this.activeArea.selectShape(Draw.Shapes.POLYGON), 'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea), 'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea), 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 8452fe8..9863ac1 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -58,6 +58,9 @@ msgstr "" msgid "Text" msgstr "" +msgid "Polygon" +msgstr "" + msgid "Fill" msgstr "" @@ -70,6 +73,11 @@ msgstr "" msgid "Full line" msgstr "" +msgid "" +"Press Enter\n" +"to mark vertices" +msgstr "" + msgid "" "Type your text\n" "and press Escape" @@ -153,6 +161,9 @@ msgstr "" msgid "Select rectangle" msgstr "" +msgid "Select polygon" +msgstr "" + msgid "Select text" msgstr "" diff --git a/prefs.js b/prefs.js index 114c7f3..b7ff66e 100644 --- a/prefs.js +++ b/prefs.js @@ -47,6 +47,7 @@ var INTERNAL_KEYBINDINGS = { 'select-line-shape': "Select line", 'select-ellipse-shape': "Select ellipse", 'select-rectangle-shape': "Select rectangle", + 'select-polygon-shape': "Select polygon", 'select-text-shape': "Select text", 'select-none-shape': "Unselect shape (free drawing)", 'toggle-fill': "Toggle fill/stroke", diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 1cd66c59bb02fdc2bc1e162edaad257b4dc06f6c..ab895c757111c49a03e8bbd59f7659fb51befd82 100644 GIT binary patch literal 3588 zcmb7HTWl0%82!;!D%Z-*wiGOQVGGV~mr^LER?rFsF)1M!#6+0g`FD5P*_mZ#wq*+v zyd;vK2^cSEBw|bi6B0-iVu%KKF+^k1CneDbnrMO#l0KLiVi3=n{daeEvoCIPa!${D z^UZ(x{%@w6U(j7sw|$|0E#O^69l1w&)4&Zk#(T)T78Qfy2za7Vh{lCN+{I7Kfg!dD zas%MF>1f(^vNGyrq-90reqY+2>DXS>aP=|Gv9*GecQr5RN@??65$Os@2tFqaj-x>V zj}-SQG=XOVvlM27iDrd)U}8S7Kw%O1K?Tm0XjNDYZU-Jx=m0MRmIDt1j{uJXD}YX5 zm4XJ20x{s!-ixVw!CadM#HhFlz7719?QrN*&xGHENh}2#yWBSys@O#0#foA*Qe)`mm&w#VQm6W-NJ~hX47<`=mD~D#X?x-8#p8)>=BqB1- zcxuMq0RIlWb9eFreQJ(>3fy$R5dMK52k29CzdFEiAphjfbM&d{4}xC=-u!gsb^6pD z@DGEJ10Q^|{XBi@`S8zxzXf)kxcC`;YUY^$PXfbTn}hYIo&$dhJbi``XTO{AI^(J5 z!fye00*AJ?|3;sh`?Uf55^(FQ=xg+;XT#5c4+3+?p9t20n(J^3{2tJJ!~K`>)WJBx z7XZ=r^Bwxs9M3iIufWt#N4L|b4#o+dhC}!C{y!(^Q}ema;4a`9D4&{s z4txxFR{7NQPl2xho0LyYe*!!OY*s!seIEV}U{Lwg^t-@Az%~EqmmDWG*C7Lb73lmU z)S1sU9z}mj&DdwkUoezQ~ zaQ6GRTj*1B9}k1y2Tqla@^S zn(f$9^D=r)!sf4U$ThRNTjcO~*hFWsu6|BBgFKyqvO} z&sF)5gnbmt67*2^;5OiKU=_gr&A$5>fR=zYMDGCDGqIJ}gJR$90N3le5%`?|`z(7n ze`uwG?*{gFv`g%7@x8GGjN=r%Csu&5EMg^~0ZRe)e7+CR0tCugn6_aib>DF{!}Roo zB@LJ&k|<3-=U9a_URdSH<7#>Sh3fT8IkvB*^sH$Wn4}VyQgKdB+FIU|c=h=OOXBS) z`SLC2bE@-s`|`RgH9)V0j;ji)Jl?(?88hYgKR2{zM?T@ZdeX=KvvwTMs9jH0bHN&oNi&`C zEAt*x&yD7E9~BwH2ShCRfQSVj5V6V!M6AvS1j@*xZQ5ymS{TwB@ts_-V%$eHS8p_3 zJ8lnE6FLqFJvowg9V}XKZ)jfqbA>GJg< zJuUa_%;DgLrq4{nj){UO#|^7I_39x!u5bY?401|W_t0R9yVAGO^85RC9We-B&F7Z-D&kjH{#soxzPl#AyAt1BFMdr;{F+MqntJg) zHSs-__?~+4Yir`yR^r#zi(gk0zpfI$t`r}XTh5iErjsu>m+*Z--3>TnHrjS*FN2;C zbVLM)jQe{qE9u1D%61oc*7R6Ana`41k2VgzP@`!zD>UskKHn{bL3>u4 z0)G#fhJ%r@F1`bSPyIfnt>MJ zVL%61H&N62(LCnjysXrQv?=g4;JvpG+@PI$DeQlN=gvkQ&cPAdsTaUr2VM=lak}Ly z?bJ=MZv*!Ni^m>orJb5_a^O5L@aWEV+Nl@8ei(cV82qQLk#=hOPk^rghjusqLOb~+F9c4WU+dCNJszjkc52#l z;8Eb_k4JiGr)Hhw;1fXK@rxhQPEG#_@D-rEx2U<4@ZW&lyZ;H=sq0~{!v<>yo*emY zigxOSu=jv-z|Uva$oZjWJtN>Zfg2Z+uhO5I{>Q=RfTvU(YT75k)4=2_hvxGvQP;qJ z9lQz~yIc8F(?0?30S>)B@jl~Iv(6m&E#PV8Pc8F;F9GRpKPh0XX(~?*!ve zFJoTtEkJAd=@#wOT(4Rju64i;6`z{%JHUOwPGzU2JqI2KijVI*&p6av-&5d^fga^g zP5(*oG_XtAscF9nuEn9=t?bmaH-WbSll|68#;4}E`oIu9HDJ(s}jv%auJCeS_nF`TR| zbU$Yl1RU|{+~Zk!!yE1LkRoCD&4HVxOTX&`dcTpk?9ocUe&y%-rfyokk+y{?Jre2i zcMFCiY~66+;ARH(A2#O${2y`{oimh0Bvj(&pSPwJ<_XBtpkoPuQ$3p<`TC_0CU+K=h2aKyl-kWOp zSoBQ>JPfcFe+{!-x$Ka@xzT4`uXbnmUHL6 z(eti1^Bk$T*hbq;6VoJvmkwaYwOy|jMa1iXV{24ZtG1}VV)t0KUFmPAdW2{AB04gi z+wGvMb8ivHsY{^y&P=;eMv09dc{fIRHnKKy2|3OEquxd`J4Io xyzs*3YQOucFUhhCo*1&+V$_{t@#H<&?G8I=-^^L9PsQ_NKd~{D#vx17{sX$BGkE|2 diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml index 1454195..616e3f7 100644 --- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml +++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml @@ -81,6 +81,11 @@ select rectangle select rectangle + + ["<Primary>y"] + select polygon + select polygon + ["<Primary>l"] select line From 5b7d1eedf43b9e4017633c8e682e2e8dd6bbb515 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 7 Jun 2020 20:05:30 +0200 Subject: [PATCH 11/67] new polyline shape Mark vertices with `Enter` and finish drawing by releasing clic, like other shapes. --- draw.js | 28 +++++++++++++----- extension.js | 1 + locale/draw-on-your-screen.pot | 6 ++++ schemas/gschemas.compiled | Bin 3588 -> 3660 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 ++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/draw.js b/draw.js index 27f3d6d..88c5e9a 100644 --- a/draw.js +++ b/draw.js @@ -56,9 +56,9 @@ const LINECAP_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child( const DASHED_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('dashed-line-symbolic.svg').get_path(); const FULL_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('full-line-symbolic.svg').get_path(); -var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5 }; +var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; const TextState = { DRAWING: 0, WRITING: 1 }; -const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon" }; +const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; const FontWeightNames = { 0: 'Normal', 1: 'Bold' }; @@ -326,7 +326,8 @@ var DrawingArea = new Lang.Class({ this._redisplay(); return Clutter.EVENT_STOP; - } else if (this.currentElement && this.currentElement.shape == Shapes.POLYGON && + } else if (this.currentElement && + (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421)) { // copy last point let lastPoint = this.currentElement.points[this.currentElement.points.length - 1]; @@ -391,7 +392,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.state = TextState.DRAWING; } - if (this.currentShape == Shapes.POLYGON) { + if (this.currentShape == Shapes.POLYGON || this.currentShape == Shapes.POLYLINE) { this.currentElement.points.push([startX, startY]); this.emit('show-osd', null, _("Press Enter\nto mark vertices"), "", -1); } @@ -463,9 +464,9 @@ var DrawingArea = new Lang.Class({ this.currentElement.transformEllipse(x, y); else if (this.currentElement.shape == Shapes.LINE && (controlPressed || this.currentElement.transform.active)) this.currentElement.transformLine(x, y); - else if (this.currentElement.shape == Shapes.POLYGON && (controlPressed || this.currentElement.transform.active)) + else if ((this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && (controlPressed || this.currentElement.transform.active)) this.currentElement.transformPolygon(x, y); - else if (this.currentElement.shape == Shapes.POLYGON) + else if (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) this.currentElement.points[this.currentElement.points.length - 1] = [x, y]; else this.currentElement.points[1] = [x, y]; @@ -974,7 +975,7 @@ const DrawingElement = new Lang.Class({ 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.POLYGON && points.length >= 2) { + } else if ((shape == Shapes.POLYGON || shape == Shapes.POLYLINE) && points.length >= 2) { this.rotate(cr, trans.angle, trans.center[0], trans.center[1]); cr.moveTo(points[0][0], points[0][1]); for (let j = 1; j < points.length; j++) { @@ -1053,6 +1054,17 @@ const DrawingElement = new Lang.Class({ row += ` ${points[i][0]},${points[i][1]}`; row += `"${transAttribute}/>`; + } else if (this.shape == Shapes.POLYLINE && 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 += ``; + } else if (this.shape == Shapes.TEXT && points.length == 2) { let transAttribute = ""; if (this.transform.angle != 0) { @@ -1554,6 +1566,8 @@ const DrawingMenu = new Lang.Class({ // change the display order of shapes if (obj == ShapeNames && i == Shapes.POLYGON) item.menu.moveMenuItem(subItem, 4); + else if (obj == ShapeNames && i == Shapes.POLYLINE) + item.menu.moveMenuItem(subItem, 5); } return GLib.SOURCE_REMOVE; }); diff --git a/extension.js b/extension.js index bc628a8..f8854a8 100644 --- a/extension.js +++ b/extension.js @@ -192,6 +192,7 @@ var AreaManager = new Lang.Class({ 'select-rectangle-shape': () => this.activeArea.selectShape(Draw.Shapes.RECTANGLE), 'select-text-shape': () => this.activeArea.selectShape(Draw.Shapes.TEXT), 'select-polygon-shape': () => this.activeArea.selectShape(Draw.Shapes.POLYGON), + 'select-polyline-shape': () => this.activeArea.selectShape(Draw.Shapes.POLYLINE), 'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea), 'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea), 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 9863ac1..e4ca8c7 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -61,6 +61,9 @@ msgstr "" msgid "Polygon" msgstr "" +msgid "Polyline" +msgstr "" + msgid "Fill" msgstr "" @@ -164,6 +167,9 @@ msgstr "" msgid "Select polygon" msgstr "" +msgid "Select polyline" +msgstr "" + msgid "Select text" msgstr "" diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index ab895c757111c49a03e8bbd59f7659fb51befd82..0f24469927fcef1b6ad543e34f371e63d8c29bd3 100644 GIT binary patch literal 3660 zcmZ`+ZEO@(6djaG`Q8c?TB&@4}pf6Ksqf!%nA)MkKZPK~yc`K(##RStu%RVhl<5=FUhh&WdI?BTTbdjCjIw4cl^?bw?XkZA&fKc}I0qjt~~#RT^7;3jVPzgjenY zW|g5DzsoTj+8khR8Rp|RQ;T9De%AntfLfpqxEHt&hyyDn?gu{*!Go|@0ZkFChW!YT zkYFEPy!@5<5SaB@tPJ^`s6j{0elzD4>a-U^kAqu*!N1!YsMEe1dN24n;MkWt$Eed@ z1U(0S4Om|H;Q{KjXF`7)d`)}^QPMtQ} zWua>TbLXBXsMD^4-U8kZ9DH@`1M0M?XTV1QZ~v7Z>a=U1p9G%=l8vI3I&H?E0AB&# zz5UAt>a^!Vp9J3^emJ_5I&J39L9PLw9Qplw>a>}E9J~Q&x|*n_PMhuafeqj(=}(*f zBjDpekJM>XKLefsPG7neV;tJda|-+$kbivlIqI|-|0Z}gCfdQR4Zl*S&GC(c9|NA2 z@o6)DFE|67IJeTKKW+AR1bhVe`Sc23r%nAh_!D50j6<7oJ_k<%6W-BJ8HYCWyaAq# zNgtbX{_*pH&3O_BtH5R%hc@GE0Ph9<%*Oq8X|vrN_;sMK@$!3&L!13N2|f?>$~d$c zXB<2QY>_%`>NmhKEWoW&r%gQ$-T+j!emsZqY1cyU0~^42zfnt_Hv4r5{0{I!hkA)R z?IqC9fWH75{%UZj)1C)?0{jzjZu<#;UC`#bm;&DfV%C8X`qQR=^*rPS6!$LL+`D4n z?|}N}gYly6&rk4G;EhwOCmDx!9C2dUTvh-l_g?I$PMhHeaHfnB@XQ$JqP))-U1GZs> z&j+SIclYHrN2q{?mJ}uQv!tJ!wQVn>nwrZNMOFl7Oz0;Nz{EDlHuIKl`%xlsMyI#O zL&LDrO4mly>+af>&k3hnNA>u+a5QwM=n({p`U&YLyVh@8p4zWv4YLqwuV1!jSU8cG ztzQCl*wE37tnCOHw^*;PT^Xa_>wad(o{hS$EcFx7#s9t=_Pg}nu6)vSw3MebL<^tP zQiEy7MoIhz!}pRhKiN$lpt^7eJv%2W*O+W_wIK|NtGYw!@IBd{iy2imyeSQ@hJJ&^ zaSJ9%$~J8WhgtX>9c?ZBYE>50MdSH3b-jWq%T)3-D#TqgJ?@&4_nKl{IamEO+R(#j z9&M#xZAE{>(v6hn*^a6ku9h@KP(HlLx&(dla0v-!eI%Y&tZRj_D{D6#uqA%iqbaV9 z=C`(#-`Zk+a_-?S6pUBszo`{_QePFQNr5krQZ zchv#cwh%NLcZZf1dwT3)3r7Z<*HI%6>%MHg_V_Yx2K0&ccwJdMd-O04jrZTJBsZ`Q|^rRANh8c{5vc8uP^go zU-DmH=I>x*)huqDGWT$N9hrA@^BERKIul&EL%-qa{bMP(2mdEtzUU{{x*Xatjw_!# z!rxOvUG4*;av!i=y=1f(^vNGyrq-90reqY+2>DXS>aP=|Gv9*GecQr5RN@??65$Os@2tFqaj-x>V zj}-SQG=XOVvlM27iDrd)U}8S7Kw%O1K?Tm0XjNDYZU-Jx=m0MRmIDt1j{uJXD}YX5 zm4XJ20x{s!-ixVw!CadM#HhFlz7719?QrN*&xGHENh}2#yWBSys@O#0#foA*Qe)`mm&w#VQm6W-NJ~hX47<`=mD~D#X?x-8#p8)>=BqB1- zcxuMq0RIlWb9eFreQJ(>3fy$R5dMK52k29CzdFEiAphjfbM&d{4}xC=-u!gsb^6pD z@DGEJ10Q^|{XBi@`S8zxzXf)kxcC`;YUY^$PXfbTn}hYIo&$dhJbi``XTO{AI^(J5 z!fye00*AJ?|3;sh`?Uf55^(FQ=xg+;XT#5c4+3+?p9t20n(J^3{2tJJ!~K`>)WJBx z7XZ=r^Bwxs9M3iIufWt#N4L|b4#o+dhC}!C{y!(^Q}ema;4a`9D4&{s z4txxFR{7NQPl2xho0LyYe*!!OY*s!seIEV}U{Lwg^t-@Az%~EqmmDWG*C7Lb73lmU z)S1sU9z}mj&DdwkUoezQ~ zaQ6GRTj*1B9}k1y2Tqla@^S zn(f$9^D=r)!sf4U$ThRNTjcO~*hFWsu6|BBgFKyqvO} z&sF)5gnbmt67*2^;5OiKU=_gr&A$5>fR=zYMDGCDGqIJ}gJR$90N3le5%`?|`z(7n ze`uwG?*{gFv`g%7@x8GGjN=r%Csu&5EMg^~0ZRe)e7+CR0tCugn6_aib>DF{!}Roo zB@LJ&k|<3-=U9a_URdSH<7#>Sh3fT8IkvB*^sH$Wn4}VyQgKdB+FIU|c=h=OOXBS) z`SLC2bE@-s`|`RgH9)V0j;ji)Jl?(?88hYgKR2{zM?T@ZdeX=KvvwTMs9jH0bHN&oNi&`C zEAt*x&yD7E9~BwH2ShCRfQSVj5V6V!M6AvS1j@*xZQ5ymS{TwB@ts_-V%$eHS8p_3 zJ8lnE6FLqFJvowg9V}XKZ)jfqbA>GJg< zJuUa_%;DgLrq4{nj){UO#|^7I_39x!u5bY?401|W_t0R9yVAGO^85RC9We-B&F7Z-D&kjH{#soxzPl#AyAt1BFMdr;{F+MqntJg) zHSs-__?~+4Yir`yR^r#zi(gk0zpfI$t`r}XTh5iErjsu>m+*Z--3>TnHrjS*FN2;C zbVLM)jselect polygon select polygon + + ["<Primary>u"] + select polyline + select polyline + ["<Primary>l"] select line From 2c632c3d3a7f5b6676b6a4410bf074d37e925bd3 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 7 Jun 2020 20:08:42 +0200 Subject: [PATCH 12/67] use `line` svg element for straight lines --- draw.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/draw.js b/draw.js index 88c5e9a..86b1aa7 100644 --- a/draw.js +++ b/draw.js @@ -1003,7 +1003,14 @@ const DrawingElement = new Lang.Class({ let color = this.eraser ? bgColor : this.color; 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'}" ` + + let attributes; + + if (isStraightLine) + attributes = `stroke="${color}" ` + + `stroke-width="${this.line.lineWidth}" ` + + `stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`; + else + attributes = `fill="${fill ? color : 'transparent'}" ` + `stroke="${color}" ` + `${fill ? '' : 'fill-opacity="0"'} ` + `stroke-width="${this.line.lineWidth}" ` + @@ -1018,7 +1025,10 @@ const DrawingElement = new Lang.Class({ 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) { + } else if (this.shape == Shapes.LINE) { + row += ``; + + } else if (this.shape == Shapes.NONE) { row += ` Date: Sun, 7 Jun 2020 21:53:08 +0200 Subject: [PATCH 14/67] fix undefined 'state' property ``` JS WARNING: [/home/.../draw.js 997]: reference to undefined property "state" ``` The state property is undefined if the element is loaded from json file. --- draw.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/draw.js b/draw.js index a3c7743..ae32b05 100644 --- a/draw.js +++ b/draw.js @@ -994,7 +994,8 @@ const DrawingElement = new Lang.Class({ } else if (shape == Shapes.TEXT && points.length == 2) { this.rotate(cr, trans.angle, trans.center[0], trans.center[1]); - if (this.state == TextState.DRAWING) + // the state property is undefined if the element is loaded from json file + if (this.state !== undefined && this.state == TextState.DRAWING) cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]); cr.selectFontFace(this.font.family, this.font.style, this.font.weight); cr.setFontSize(Math.abs(points[1][1] - points[0][1])); From b4eb418c891eb19197280e8c126508fc6df517ee Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 8 Jun 2020 14:30:47 +0200 Subject: [PATCH 15/67] key label translations Get key label translations by using Gtk. --- draw.js | 44 +++++++++++++++++++++------- extension.js | 4 ++- locale/draw-on-your-screen.pot | 52 ++++++++++++++++------------------ prefs.js | 25 +++++++++------- 4 files changed, 76 insertions(+), 49 deletions(-) diff --git a/draw.js b/draw.js index ae32b05..11f4447 100644 --- a/draw.js +++ b/draw.js @@ -401,7 +401,7 @@ var DrawingArea = new Lang.Class({ if (this.currentShape == Shapes.POLYGON || this.currentShape == Shapes.POLYLINE) { this.currentElement.points.push([startX, startY]); - this.emit('show-osd', null, _("Press Enter\nto mark vertices"), "", -1); + this.emit('show-osd', null, _("Press %s\nto mark vertices").format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); } this.motionHandler = this.connect('motion-event', (actor, event) => { @@ -444,7 +444,7 @@ var DrawingArea = new Lang.Class({ // start writing this.currentElement.state = TextState.WRITING; this.currentElement.text = ''; - this.emit('show-osd', null, _("Type your text\nand press Escape"), "", -1); + this.emit('show-osd', null, _("Type your text\nand press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); @@ -1238,6 +1238,32 @@ var DrawingHelper = new Lang.Class({ this.parent(params); this.monitor = monitor; this.hide(); + this.settings = Convenience.getSettings(); + + this.settingHandler = this.settings.connect('changed', this._onSettingChanged.bind(this)); + this.connect('destroy', () => this.settings.disconnect(this.settingHandler)); + }, + + _onSettingChanged: function(settings, key) { + if (key == 'toggle-help') + this._updateHelpKeyLabel(); + + if (this.vbox) { + this.vbox.destroy(); + this.vbox = null; + } + }, + + _updateHelpKeyLabel: function() { + let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv('toggle-help')[0]); + this._helpKeyLabel = Gtk.accelerator_get_label(keyval, mods); + }, + + get helpKeyLabel() { + if (!this._helpKeyLabel) + this._updateHelpKeyLabel(); + + return this._helpKeyLabel; }, _populate: function() { @@ -1245,17 +1271,15 @@ var DrawingHelper = new Lang.Class({ this.add_actor(this.vbox); this.vbox.add_child(new St.Label({ text: _("Global") })); - let settings = Convenience.getSettings(); - for (let settingKey in Prefs.GLOBAL_KEYBINDINGS) { let hbox = new St.BoxLayout({ vertical: false }); if (settingKey.indexOf('-separator-') != -1) { this.vbox.add_child(hbox); continue; } - if (!settings.get_strv(settingKey)[0]) + if (!this.settings.get_strv(settingKey)[0]) continue; - let [keyval, mods] = Gtk.accelerator_parse(settings.get_strv(settingKey)[0]); + let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv(settingKey)[0]); hbox.add_child(new St.Label({ text: _(Prefs.GLOBAL_KEYBINDINGS[settingKey]) })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); this.vbox.add_child(hbox); @@ -1270,7 +1294,7 @@ var DrawingHelper = new Lang.Class({ } let hbox = new St.BoxLayout({ vertical: false }); hbox.add_child(new St.Label({ text: _(Prefs.OTHER_SHORTCUTS[i].desc) })); - hbox.add_child(new St.Label({ text: _(Prefs.OTHER_SHORTCUTS[i].shortcut), x_expand: true })); + hbox.add_child(new St.Label({ text: Prefs.OTHER_SHORTCUTS[i].shortcut, x_expand: true })); this.vbox.add_child(hbox); } @@ -1282,9 +1306,9 @@ var DrawingHelper = new Lang.Class({ continue; } let hbox = new St.BoxLayout({ vertical: false }); - if (!settings.get_strv(settingKey)[0]) + if (!this.settings.get_strv(settingKey)[0]) continue; - let [keyval, mods] = Gtk.accelerator_parse(settings.get_strv(settingKey)[0]); + let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv(settingKey)[0]); hbox.add_child(new St.Label({ text: _(Prefs.INTERNAL_KEYBINDINGS[settingKey]) })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); this.vbox.add_child(hbox); @@ -1339,7 +1363,7 @@ var DrawingHelper = new Lang.Class({ transition: 'easeOutQuad', onComplete: this.hide.bind(this) }); - }, + } }); const getActor = function(object) { diff --git a/extension.js b/extension.js index f8854a8..4330d8c 100644 --- a/extension.js +++ b/extension.js @@ -336,7 +336,9 @@ var AreaManager = new Lang.Class({ // increase OSD display time let hideTimeoutSave = OsdWindow.HIDE_TIMEOUT; try { OsdWindow.HIDE_TIMEOUT = 2000; } catch(e) { /* HIDE_TIMEOUT is not exported with 'var' */ } - Main.osdWindowManager.show(currentIndex, this.enterGicon, _("Press Ctrl + F1 for help") + "\n\n" + _("Entering drawing mode"), null); + let label = _("Press %s for help").format(this.activeArea.helper.helpKeyLabel) + "\n\n" + _("Entering drawing mode"); + Main.osdWindowManager.show(currentIndex, this.enterGicon, label, null); + Main.osdWindowManager._osdWindows[this.areas.indexOf(this.activeArea)]._label.get_clutter_text().set_use_markup(true); try { OsdWindow.HIDE_TIMEOUT = hideTimeoutSave; } catch(e) {} } } diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index e4ca8c7..baefa57 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -36,7 +36,8 @@ msgstr "" msgid "Leaving drawing mode" msgstr "" -msgid "Press Ctrl + F1 for help" +# %s is a key label +msgid "Press %s for help" msgstr "" msgid "Entering drawing mode" @@ -76,14 +77,16 @@ msgstr "" msgid "Full line" msgstr "" +# %s is a key label msgid "" -"Press Enter\n" +"Press %s\n" "to mark vertices" msgstr "" +# %s is a key label msgid "" "Type your text\n" -"and press Escape" +"and press %s" msgstr "" msgid "Screenshot" @@ -133,7 +136,7 @@ msgstr "" msgid "About" msgstr "" -# GLOBAL_KEYBINDINGS +#: GLOBAL_KEYBINDINGS msgid "Enter/leave drawing mode" msgstr "" @@ -141,7 +144,7 @@ msgstr "" msgid "Erase all drawings" msgstr "" -# INTERNAL_KEYBINDINGS +#: INTERNAL_KEYBINDINGS msgid "Undo last brushstroke" msgstr "" @@ -197,9 +200,9 @@ msgstr "" msgid "Change linecap" msgstr "" -# already in draw.js -#msgid "Dashed line" -#msgstr "" +#: already in draw.js +#:msgid "Dashed line" +#:msgstr "" msgid "Change font family (generic name)" msgstr "" @@ -228,9 +231,9 @@ msgstr "" msgid "Open next drawing" msgstr "" -# already in draw.js -#msgid "Save drawing" -#msgstr "" +#: already in draw.js +#:msgid "Save drawing" +#:msgstr "" msgid "Save drawing as a SVG file" msgstr "" @@ -241,7 +244,7 @@ msgstr "" msgid "Show help" msgstr "" -# OTHER_SHORTCUTS +#: OTHER_SHORTCUTS msgid "Draw" msgstr "" @@ -261,9 +264,6 @@ msgstr "" msgid "Transform shape (when drawing)" msgstr "" -msgid "Ctrl key" -msgstr "" - msgid "Increment/decrement line width" msgstr "" @@ -273,40 +273,36 @@ msgstr "" msgid "Select color" msgstr "" -msgid "Ctrl+1...9" +# %s are key labels (Ctrl+F1 and Ctrl+F9) +msgid "%s … %s" msgstr "" msgid "Select eraser" msgstr "" -msgid "Shift key held" +# %s is a key label +msgid "%s held" msgstr "" msgid "Ignore pointer movement" msgstr "" -msgid "Space key held" -msgstr "" - msgid "Leave" msgstr "" -msgid "Escape key" +#: About page + +# You are free to translate the extension name, that is displayed in About page, or not. +msgid "Draw On You Screen" msgstr "" -# About page - -# you are free to translate the extension name -#msgid "Draw On You Screen" -#msgstr "" - msgid "Version %d" msgstr "" msgid "Start drawing with Super+Alt+D and save your beautiful work by taking a screenshot" msgstr "" -# Prefs page +#: Prefs page msgid "Global" msgstr "" diff --git a/prefs.js b/prefs.js index b7ff66e..b46046a 100644 --- a/prefs.js +++ b/prefs.js @@ -77,16 +77,21 @@ var INTERNAL_KEYBINDINGS = { 'toggle-help': "Show help" }; +function getKeyLabel(accel) { + let [keyval, mods] = Gtk.accelerator_parse(accel); + return Gtk.accelerator_get_label(keyval, mods); +} + var OTHER_SHORTCUTS = [ - { desc: "Draw", shortcut: "Left click" }, - { desc: "Menu", shortcut: "Right click" }, - { desc: "Toggle fill/stroke", 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" }, - { desc: "Ignore pointer movement", shortcut: "Space key held" }, - { desc: "Leave", shortcut: "Escape key" } + { desc: "Draw", get shortcut() { return _("Left click"); } }, + { desc: "Menu", get shortcut() { return _("Right click"); } }, + { desc: "Toggle fill/stroke", get shortcut() { return _("Center click"); } }, + { desc: "Transform shape (when drawing)", shortcut: getKeyLabel('') }, + { desc: "Increment/decrement line width", get shortcut() { return _("Scroll"); } }, + { desc: "Select color", get shortcut() { return _("%s … %s").format(getKeyLabel('1'), getKeyLabel('9')); } }, + { desc: "Select eraser", get shortcut() { return _("%s held").format(getKeyLabel('')); } }, + { desc: "Ignore pointer movement", get shortcut() { return _("%s held").format(getKeyLabel('space')); } }, + { desc: "Leave", shortcut: getKeyLabel('Escape') } ]; function init() { @@ -270,7 +275,7 @@ const PrefsPage = new GObject.Class({ let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN }); let otherLabel = new Gtk.Label({ label: _(OTHER_SHORTCUTS[i].desc) }); otherLabel.set_halign(1); - let otherLabel2 = new Gtk.Label({ label: _(OTHER_SHORTCUTS[i].shortcut) }); + let otherLabel2 = new Gtk.Label({ label: OTHER_SHORTCUTS[i].shortcut }); otherBox.pack_start(otherLabel, true, true, 4); otherBox.pack_start(otherLabel2, false, false, 4); listBox.add(otherBox); From 9bde4d3013406984eb357b59304d8379fe5520f9 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 8 Jun 2020 15:26:55 +0200 Subject: [PATCH 16/67] add "Open preferences" shortcut Available since GS 3.36, so the shortcut is hidden for previous versions. --- extension.js | 12 +++++++++++- locale/draw-on-your-screen.pot | 3 +++ prefs.js | 6 ++++++ schemas/gschemas.compiled | Bin 3660 -> 3732 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 +++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/extension.js b/extension.js index 4330d8c..9a9afce 100644 --- a/extension.js +++ b/extension.js @@ -198,7 +198,8 @@ var AreaManager = new Lang.Class({ 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), 'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this), 'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea), - 'open-user-stylesheet': this.openUserStyleFile.bind(this) + 'open-user-stylesheet': this.openUserStyleFile.bind(this), + 'open-preferences': this.openPreferences.bind(this) }; for (let key in this.internalKeybindings) { @@ -229,6 +230,15 @@ var AreaManager = new Lang.Class({ } }, + openPreferences: function() { + // since GS 3.36 + if (ExtensionUtils.openPrefs) { + if (this.activeArea) + this.toggleDrawing(); + ExtensionUtils.openPrefs(); + } + }, + openUserStyleFile: function() { if (!this.userStyleFile.query_exists(null)) { if (!this.userStyleFile.get_parent().query_exists(null)) diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index baefa57..688baa7 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -241,6 +241,9 @@ msgstr "" msgid "Edit style" msgstr "" +msgid "Open preferences" +msgstr "" + msgid "Show help" msgstr "" diff --git a/prefs.js b/prefs.js index b46046a..4e958f0 100644 --- a/prefs.js +++ b/prefs.js @@ -25,12 +25,14 @@ const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Mainloop = imports.mainloop; +const Config = imports.misc.config; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _GTK = imports.gettext.domain('gtk30').gettext; +const GS_VERSION = Config.PACKAGE_VERSION; const MARGIN = 10; var GLOBAL_KEYBINDINGS = { @@ -74,9 +76,13 @@ var INTERNAL_KEYBINDINGS = { 'save-as-json': "Save drawing", 'save-as-svg': "Save drawing as a SVG file", 'open-user-stylesheet': "Edit style", + 'open-preferences': "Open preferences", 'toggle-help': "Show help" }; +if (GS_VERSION < "3.36") + delete INTERNAL_KEYBINDINGS['open-preferences']; + function getKeyLabel(accel) { let [keyval, mods] = Gtk.accelerator_parse(accel); return Gtk.accelerator_get_label(keyval, mods); diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 0f24469927fcef1b6ad543e34f371e63d8c29bd3..b90b1027f1cade72a87eb947d02a669b29e1591c 100644 GIT binary patch literal 3732 zcmaJ^TWl0n7#@^D1*FSWTB^3FumxtfrB?_R(8^6sAOzDG1*SV^cc-15S!ZV3Zb2Rt zBL+`}T;-FZaNN6rDgZ+$ z_rx#*dL}Rn;E**jB%q0T67#_ef%_yDgX@9&B_0G54H8Sh4^6;Q{9g7SNW$I(tdLj< zP6I8#{sU*;Wq*uOtum-2!CQfi!@pdmo%&wbd%?rNCTXXp{UG=xuvyxvX+IAh1M0fV z?d*qoKJ2%^Q>H7*7U@q-{{*-h*edPRw66#60d9VrdWro|(_RE004~3{eRq&5MUsANtPCXa)Kft$vrW@&M+NtNjo~TxoMxgh|x%X+Oro9Wi z1xQ%?hH0ms1$!^}dEo6&x1XV%dLHaW@GHO-@6boIQ*+!S;IqJ~FJ`<uDm!&^7*LxCt0Wi1kUPn7M^JEPC8_=7)beMK(&NG3;Zv?!(U-!^X z?Z*wg1$gt$51-IZ&H49&i@@opkM5(LdMfP0;8Eb#w}-aVPCXm;W8kyE{;l;t(@xEC zuYi97el0Zk-<_KM-vUp=Moe6D{-!@Qze@vH1v=J!xQ2G>1nldCfU<1@t#> z^6LOK=d%c$26|*a)c$@4?*X2Wc4~hzP~@E4Be3YABmVddz*!SoQrQc*bF0$+?%NEJ0Nk9U01I5^Y(!I}0C&UAEe zrYAW#aoo6`oy(a*#W7oq&3|JH@|nHMv3cU^!X5Bzc=1X0HOZD)8=u_|Q>?vF86Q6@ z+`Lwl2@%H8lzz6WtGeN8eWuWZ{@sFYBaKYW^;BdB(<^KzUMOiMN;9YEf2^V-1`WI9 zs{O8QMUmX5<;3nDd&ok0p|6}X=hJT+R@h&E<$Q7HFp7wLFy+usNI%(6&N2MyhVOHg z{Sw zWn(AG1%%@kWqZT=-whl3NBebyuH!1i7|Q;+wj9zTh!IB*+m$QV*QaF$a*kcH#y@W4 z*C+j4ZBVG1tGa_s^XR=C-$m8p(2mgU$=;91_p#5L(c<|$Ajg@kP{qfK7p4XAjL|h0 zn=D)(;&0eG&L+-EM|OImB2hU>JU#v!WxDScp)xHL;3IyisKZ6D9Unx_tg}@yJQ1U155=_`f&%r83!x_>VOr13N!<}ck+&Y z7ZATc&%tjTWBy&3_j}%tdCyG(DE+v1D9Qss6Ywz52=EwQ3d{v?1@Z6A2{7-}D}hG< znMMxEzGkrm#S$ruizC+?#KbU7G>zh+$@j=CCPvt9WI{XEs3BqG^5cJ1LcbyD7v!H` zJ!1X1!Ta&!$wNJzbe>-2ctORE*K1wY>&doWI3MI6H`;h!#ZL=&@4}pf6Ksqf!%nA)MkKZPK~yc`K(##RStu%RVhl<5=FUhh&WdI?BTTbdjCjIw4cl^?bw?XkZA&fKc}I0qjt~~#RT^7;3jVPzgjenY zW|g5DzsoTj+8khR8Rp|RQ;T9De%AntfLfpqxEHt&hyyDn?gu{*!Go|@0ZkFChW!YT zkYFEPy!@5<5SaB@tPJ^`s6j{0elzD4>a-U^kAqu*!N1!YsMEe1dN24n;MkWt$Eed@ z1U(0S4Om|H;Q{KjXF`7)d`)}^QPMtQ} zWua>TbLXBXsMD^4-U8kZ9DH@`1M0M?XTV1QZ~v7Z>a=U1p9G%=l8vI3I&H?E0AB&# zz5UAt>a^!Vp9J3^emJ_5I&J39L9PLw9Qplw>a>}E9J~Q&x|*n_PMhuafeqj(=}(*f zBjDpekJM>XKLefsPG7neV;tJda|-+$kbivlIqI|-|0Z}gCfdQR4Zl*S&GC(c9|NA2 z@o6)DFE|67IJeTKKW+AR1bhVe`Sc23r%nAh_!D50j6<7oJ_k<%6W-BJ8HYCWyaAq# zNgtbX{_*pH&3O_BtH5R%hc@GE0Ph9<%*Oq8X|vrN_;sMK@$!3&L!13N2|f?>$~d$c zXB<2QY>_%`>NmhKEWoW&r%gQ$-T+j!emsZqY1cyU0~^42zfnt_Hv4r5{0{I!hkA)R z?IqC9fWH75{%UZj)1C)?0{jzjZu<#;UC`#bm;&DfV%C8X`qQR=^*rPS6!$LL+`D4n z?|}N}gYly6&rk4G;EhwOCmDx!9C2dUTvh-l_g?I$PMhHeaHfnB@XQ$JqP))-U1GZs> z&j+SIclYHrN2q{?mJ}uQv!tJ!wQVn>nwrZNMOFl7Oz0;Nz{EDlHuIKl`%xlsMyI#O zL&LDrO4mly>+af>&k3hnNA>u+a5QwM=n({p`U&YLyVh@8p4zWv4YLqwuV1!jSU8cG ztzQCl*wE37tnCOHw^*;PT^Xa_>wad(o{hS$EcFx7#s9t=_Pg}nu6)vSw3MebL<^tP zQiEy7MoIhz!}pRhKiN$lpt^7eJv%2W*O+W_wIK|NtGYw!@IBd{iy2imyeSQ@hJJ&^ zaSJ9%$~J8WhgtX>9c?ZBYE>50MdSH3b-jWq%T)3-D#TqgJ?@&4_nKl{IamEO+R(#j z9&M#xZAE{>(v6hn*^a6ku9h@KP(HlLx&(dla0v-!eI%Y&tZRj_D{D6#uqA%iqbaV9 z=C`(#-`Zk+a_-?S6pUBszo`{_QePFQNr5krQZ zchv#cwh%NLcZZf1dwT3)3r7Z<*HI%6>%MHg_V_Yx2K0&ccwJdMd-O04jrZTJBsZ`Q|^rRANh8c{5vc8uP^go zU-DmH=I>x*)huqDGWT$N9hrA@^BERKIul&EL%-qa{bMP(2mdEtzUU{{x*Xatjw_!# z!rxOvUG4*;av!i=y=Open next json file Open next json file + + ["<Primary>comma"] + Open preferences + Open preferences + ["<Primary>F1"] toggle help From 04894a90311ab4beebba6dee2b43832204d27d19 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 8 Jun 2020 20:50:23 +0200 Subject: [PATCH 17/67] add "file-rule" drawing element attribute --- data/default.css | 3 + data/icons/fillrule-evenodd-symbolic.svg | 3 + data/icons/fillrule-nonzero-symbolic.svg | 3 + draw.js | 72 +++++++++++++----- extension.js | 1 + locale/draw-on-your-screen.pot | 9 +++ prefs.js | 1 + schemas/gschemas.compiled | Bin 3732 -> 3920 bytes ...extensions.draw-on-your-screen.gschema.xml | 9 ++- 9 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 data/icons/fillrule-evenodd-symbolic.svg create mode 100644 data/icons/fillrule-nonzero-symbolic.svg diff --git a/data/default.css b/data/default.css index 67f91a5..61e858f 100644 --- a/data/default.css +++ b/data/default.css @@ -9,6 +9,8 @@ * 0 : miter, 1 : round, 2 : bevel * line-cap (no string): * 0 : butt, 1 : round, 2 : square + * fill-rule (no string): + * 0 : nonzero (winding in Cairo), 1 : evenodd * * dash: * dash-array-on is the length of dashes (no dashes if 0, you can put 0.1 to get dots or square according to line-cap). @@ -33,6 +35,7 @@ -drawing-line-width: 5px; -drawing-line-join: 1; -drawing-line-cap: 1; + -drawing-fill-rule: 0; -drawing-dash-array-on: 5px; -drawing-dash-array-off: 15px; -drawing-dash-offset: 0px; diff --git a/data/icons/fillrule-evenodd-symbolic.svg b/data/icons/fillrule-evenodd-symbolic.svg new file mode 100644 index 0000000..a74de4c --- /dev/null +++ b/data/icons/fillrule-evenodd-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/data/icons/fillrule-nonzero-symbolic.svg b/data/icons/fillrule-nonzero-symbolic.svg new file mode 100644 index 0000000..a3b9b2b --- /dev/null +++ b/data/icons/fillrule-nonzero-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/draw.js b/draw.js index 11f4447..445ec11 100644 --- a/draw.js +++ b/draw.js @@ -49,18 +49,22 @@ const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const GS_VERSION = Config.PACKAGE_VERSION; -const FILL_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('fill-symbolic.svg').get_path(); -const STROKE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('stroke-symbolic.svg').get_path(); -const LINEJOIN_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('linejoin-symbolic.svg').get_path(); -const LINECAP_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('linecap-symbolic.svg').get_path(); -const DASHED_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('dashed-line-symbolic.svg').get_path(); -const FULL_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('full-line-symbolic.svg').get_path(); +const ICON_DIR = Me.dir.get_child('data').get_child('icons'); +const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path(); +const STROKE_ICON_PATH = ICON_DIR.get_child('stroke-symbolic.svg').get_path(); +const LINEJOIN_ICON_PATH = ICON_DIR.get_child('linejoin-symbolic.svg').get_path(); +const LINECAP_ICON_PATH = ICON_DIR.get_child('linecap-symbolic.svg').get_path(); +const FILLRULE_NONZERO_ICON_PATH = ICON_DIR.get_child('fillrule-nonzero-symbolic.svg').get_path(); +const FILLRULE_EVENODD_ICON_PATH = ICON_DIR.get_child('fillrule-evenodd-symbolic.svg').get_path(); +const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get_path(); +const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path(); var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; const TextState = { DRAWING: 0, WRITING: 1 }; const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; +const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; const FontWeightNames = { 0: 'Normal', 1: 'Bold' }; const FontStyleNames = { 0: 'Normal', 1: 'Italic', 2: 'Oblique' }; const FontFamilyNames = { 0: 'Default', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; @@ -155,6 +159,7 @@ var DrawingArea = new Lang.Class({ this.currentLineWidth = themeNode.get_length('-drawing-line-width'); this.currentLineJoin = themeNode.get_double('-drawing-line-join'); this.currentLineCap = themeNode.get_double('-drawing-line-cap'); + this.currentFillRule = themeNode.get_double('-drawing-fill-rule'); this.dashArray = [themeNode.get_length('-drawing-dash-array-on'), themeNode.get_length('-drawing-dash-array-off')]; this.dashOffset = themeNode.get_length('-drawing-dash-offset'); let font = themeNode.get_font(); @@ -179,6 +184,7 @@ var DrawingArea = new Lang.Class({ this.currentLineWidth = (this.currentLineWidth > 0) ? this.currentLineWidth : 3; this.currentLineJoin = ([0, 1, 2].indexOf(this.currentLineJoin) != -1) ? this.currentLineJoin : Cairo.LineJoin.ROUND; this.currentLineCap = ([0, 1, 2].indexOf(this.currentLineCap) != -1) ? this.currentLineCap : Cairo.LineCap.ROUND; + this.currentFillRule = ([0, 1].indexOf(this.currentFillRule) != -1) ? this.currentFillRule : Cairo.FillRule.WINDING; this.currentFontFamilyId = 0; this.currentFontWeight = this.currentFontWeight > 500 ? 1 : 0 ; // font style enum order of Cairo and Pango are different @@ -384,6 +390,7 @@ var DrawingArea = new Lang.Class({ line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap }, dash: { array: this.dashedLine ? this.dashArray : [0, 0] , offset: this.dashedLine ? this.dashOffset : 0 }, fill: this.fill, + fillRule: this.currentFillRule, eraser: eraser, transform: { active: false, center: [0, 0], angle: 0, startAngle: 0, ratio: 1 }, text: '', @@ -647,6 +654,11 @@ var DrawingArea = new Lang.Class({ this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), "", -1); }, + toggleFillRule: function() { + this.currentFillRule = this.currentFillRule == 1 ? 0 : this.currentFillRule + 1; + this.emit('show-osd', null, _(FillRuleNames[this.currentFillRule]), "", -1); + }, + toggleFontWeight: function() { this.currentFontWeight = this.currentFontWeight == 1 ? 0 : this.currentFontWeight + 1; if (this.currentElement) { @@ -920,6 +932,10 @@ const DrawingElement = new Lang.Class({ _init: function(params) { for (let key in params) this[key] = params[key]; + + // compatibility with json generated by old extension versions + if (params.fillRule === undefined) + this.fillRule = Cairo.FillRule.WINDING; }, // toJSON is called by JSON.stringify @@ -930,6 +946,7 @@ const DrawingElement = new Lang.Class({ line: this.line, dash: this.dash, fill: this.fill, + fillRule: this.fillRule, eraser: this.eraser, transform: this.transform, text: this.text, @@ -942,6 +959,7 @@ const DrawingElement = new Lang.Class({ cr.setLineCap(this.line.lineCap); cr.setLineJoin(this.line.lineJoin); cr.setLineWidth(this.line.lineWidth); + cr.setFillRule(this.fillRule); if (this.dash.array[0] > 0 && this.dash.array[1] > 0) cr.setDash(this.dash.array, this.dash.offset); @@ -1019,8 +1037,8 @@ const DrawingElement = new Lang.Class({ `stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`; else attributes = `fill="${fill ? color : 'transparent'}" ` + + (fill ? `fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}" ` : `fill-opacity="0" `) + `stroke="${color}" ` + - `${fill ? '' : 'fill-opacity="0"'} ` + `stroke-width="${this.line.lineWidth}" ` + `stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}" ` + `stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`; @@ -1404,6 +1422,8 @@ const DrawingMenu = new Lang.Class({ this.strokeIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(STROKE_ICON_PATH) }); this.fillIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILL_ICON_PATH) }); + this.fillRuleNonzeroIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILLRULE_NONZERO_ICON_PATH) }); + this.fillRuleEvenoddIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILLRULE_EVENODD_ICON_PATH) }); this.linejoinIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(LINEJOIN_ICON_PATH) }); this.linecapIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(LINECAP_ICON_PATH) }); this.fullLineIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FULL_LINE_ICON_PATH) }); @@ -1463,9 +1483,12 @@ const DrawingMenu = new Lang.Class({ this.menu.addAction(_("Smooth"), this.area.smoothLastElement.bind(this.area), 'format-text-strikethrough-symbolic'); this._addSeparator(this.menu); - this._addSubMenuItem(this.menu, null, ShapeNames, this.area, 'currentShape', this.updateSectionVisibility.bind(this)); + this._addSubMenuItem(this.menu, null, ShapeNames, this.area, 'currentShape', this._updateSectionVisibility.bind(this)); this._addColorSubMenuItem(this.menu); - this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill'); + this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill', this._updateSectionVisibility.bind(this)); + this.fillSection = new PopupMenu.PopupMenuSection(); + this._addSubMenuItem(this.fillSection, this.fillRuleNonzeroIcon, FillRuleNames, this.area, 'currentFillRule', this._updateFillRuleIcon.bind(this)); + this.menu.addMenuItem(this.fillSection); this._addSeparator(this.menu); let lineSection = new PopupMenu.PopupMenuSection(); @@ -1489,10 +1512,10 @@ const DrawingMenu = new Lang.Class({ this.fontSection = fontSection; let manager = Extension.manager; - this._addSwitchItemWithCallback(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager)); - this._addSwitchItemWithCallback(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area)); - this._addSwitchItemWithCallback(this.menu, _("Add a grid overlay"), this.area.hasGrid, this.area.toggleGrid.bind(this.area)); - this._addSwitchItemWithCallback(this.menu, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area)); + 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 grid overlay"), this.area.hasGrid, this.area.toggleGrid.bind(this.area)); + this._addSimpleSwitchItem(this.menu, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area)); this._addSeparator(this.menu); this._addDrawingNameItem(this.menu); @@ -1503,22 +1526,35 @@ const DrawingMenu = new Lang.Class({ this.menu.addAction(_("Edit style"), manager.openUserStyleFile.bind(manager), 'document-page-setup-symbolic'); this.menu.addAction(_("Show help"), () => { this.close(); this.area.toggleHelp(); }, 'preferences-desktop-keyboard-shortcuts-symbolic'); - this.updateSectionVisibility(); + this._updateSectionVisibility(); + this._updateFillRuleIcon(); }, - updateSectionVisibility: function() { + _updateSectionVisibility: function() { if (this.area.currentShape != Shapes.TEXT) { this.lineSection.actor.show(); this.fontSection.actor.hide(); this.fillItem.setSensitive(true); + this.fillSection.setSensitive(true); } else { this.lineSection.actor.hide(); this.fontSection.actor.show(); this.fillItem.setSensitive(false); + this.fillSection.setSensitive(false); } + + if (this.area.fill) + this.fillSection.actor.show(); + else + this.fillSection.actor.hide(); }, - _addSwitchItem: function(menu, label, iconFalse, iconTrue, target, targetProperty) { + _updateFillRuleIcon: function() { + let fillRuleIcon = this.area.currentFillRule == Cairo.FillRule.EVEN_ODD ? this.fillRuleEvenoddIcon : this.fillRuleNonzeroIcon; + this.fillSection.firstMenuItem.icon.set_gicon(fillRuleIcon); + }, + + _addSwitchItem: function(menu, label, iconFalse, iconTrue, target, targetProperty, onToggled) { let item = new PopupMenu.PopupSwitchMenuItem(label, target[targetProperty]); item.icon = new St.Icon({ style_class: 'popup-menu-icon' }); @@ -1528,12 +1564,14 @@ const DrawingMenu = new Lang.Class({ item.connect('toggled', (item, state) => { target[targetProperty] = state; item.icon.set_gicon(target[targetProperty] ? iconTrue : iconFalse); + if (onToggled) + onToggled(); }); menu.addMenuItem(item); return item; }, - _addSwitchItemWithCallback: function(menu, label, active, onToggled) { + _addSimpleSwitchItem: function(menu, label, active, onToggled) { let item = new PopupMenu.PopupSwitchMenuItem(label, active); item.connect('toggled', onToggled); menu.addMenuItem(item); diff --git a/extension.js b/extension.js index 9a9afce..17bbb08 100644 --- a/extension.js +++ b/extension.js @@ -184,6 +184,7 @@ var AreaManager = new Lang.Class({ 'decrement-line-width-more': () => this.activeArea.incrementLineWidth(-5), 'toggle-linejoin': this.activeArea.toggleLineJoin.bind(this.activeArea), 'toggle-linecap': this.activeArea.toggleLineCap.bind(this.activeArea), + 'toggle-fill-rule': this.activeArea.toggleFillRule.bind(this.activeArea), 'toggle-dash' : this.activeArea.toggleDash.bind(this.activeArea), 'toggle-fill' : this.activeArea.toggleFill.bind(this.activeArea), 'select-none-shape': () => this.activeArea.selectShape(Draw.Shapes.NONE), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 688baa7..c221e6d 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -200,6 +200,9 @@ msgstr "" msgid "Change linecap" msgstr "" +msgid "Change fill rule" +msgstr "" + #: already in draw.js #:msgid "Dashed line" #:msgstr "" @@ -372,6 +375,12 @@ msgstr "" #msgid "Bevel" #msgstr "" +#msgid "Nonzero" +#msgstr "" + +#msgid "Evenodd" +#msgstr "" + #msgid "Normal" #msgstr "" diff --git a/prefs.js b/prefs.js index 4e958f0..9c7aaad 100644 --- a/prefs.js +++ b/prefs.js @@ -60,6 +60,7 @@ var INTERNAL_KEYBINDINGS = { 'decrement-line-width-more': "Decrement line width even more", 'toggle-linejoin': "Change linejoin", 'toggle-linecap': "Change linecap", + 'toggle-fill-rule': "Change fill rule", 'toggle-dash': "Dashed line", '-separator-3': '', 'toggle-font-family': "Change font family (generic name)", diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index b90b1027f1cade72a87eb947d02a669b29e1591c..ce2063209e49a15b23db822e9aff504314b53201 100644 GIT binary patch literal 3920 zcmZu!ZEO@p7+w^#0zyH)3PlR?aeVBR(oYCh&_68%#Wf8Y-YBqmA>;`7YS?QV}YdHOv2-aGHS z^Umzd?8ave&ot~nQ-8C-J4-ropR$bsj~yOpCiAzX)~Ou=FML4L#!c6>yZGBY83t{O zwgub;I9@iHwVk|3`Z-})NwGH&wr@JNpUikhzwX$2(J6SkpZ0{X*;Y$5N-F(hU+h1Q z`|&&$7zdzh?LnYQVLZ4J6JVPNOaiKbslW_?W1J1l1s+vc0A3iwVrU|vuoO%z1C|3T z03ApI^$HE(RRG69ocVtIYqen3X|mR*wShMQ!M>k6Xs4b6`)=?aVD|G19NMWTzr@jcq9X&(Y#1#17U^=PM_2>VU&7!>Q&g*(->Q%{9`7I-dj z^OwWhXs4b8`%3V7pxWNQmv(B#=>qQnE_ItTXs2eJ9JmM^KU>P5n)x3B9|NA;dwq!h z)DOdc8hjaeO4+GtzXJXT`2Ey;SqEyyslp`J03FJon*IrJC-6e6eu43+r^CJ*ya%`( z9R7lKYUX(ed=hwC#i3@LLGTc;^w0V#`crdWu7Pg@Ju5fL`ctz$RS#*}JmBosg2{0@U}0i7xi zHREvM*8!W9otpNQ;4WZX!>8jJhk6F=IdGA7_3l#h-Q5pfhnHq^Ie%!GKiCC&|81;g z9BNrl@CU$w*9SkMow^3`&x3yh=G1&F>qE`@41;e0XMTKp7X7I?t}48wO+exC9cO8$ zW*s`g&jBZPpX;Wbnsyib3Xn>O2HL5a&r$Gk;KrB9S81na{)6D}fcL-IHb^`5Bd}iq zUk9|=zuuvpn)8cB+BD#=e4Si})Le&k;1=SE&(_eNx*GOQ@Qc9Yfi*H8YUbmD3xKt4 z*Aw)oW_^x;KL_5ud-W^YsTuz~cnCQ5*1j9GQ!~yr@NM9YlgqBqPF)9k9X7TG;KZ>v zKct;n<^w(gTzdJy1n$k$uHjx%Lm*spY(2ALqUvIN7Wv zbj!3wk7L^K(6qH(o|!kiVtWr#PZ<7E<&-kgy;;vG*cs^-`K3nsWj#}RgmJR!cgGdB zUhoBuhM;H(oF0OQQ|RX?KVMiP9q6uO6|*=@{G8#6xL>jCXY>i(@bw{ z88dAJj;Ci#-$+?PC9Wk>h>Wq;Luw`o};$7Pi3 zR;sV;9*0>xUSsssSdJH)4wmcQ`6!g@#rbtT(Puh^a8_dRyNs;Z)#3EpSelh(Xo~q^ zTuo6PP31hw<6`OM0+OSPwKL;+*tnLIRz^5J+5Zj~8x4E+M8+9epS9a_W_Qp&fH$); zz2=I#H%Iw3m-CCyYDx|x{`1~g2gK)dHJ-z9qCTsmr`6^9sP*aZDHtC1B~KV= z6mMs%`eX!d#(9BdEa$%8%mg_-?|9tg;{IwqqPy*dRNxuuK&zE26I(ErZkW%2ifh{U z#*N2Ap!Jzd)K0~Bt%9#rFN&}EfG^KI`);86QH8Oc*k%y#$1RT&UjwF{V;O#+W7lHQqWlFaZDGpk zsEQvC7>}1x@yc$7pNrz4L(|FNd}SbcNMRG}7M`%v!jIKC?d0=@>JM#v)5nKT5E}%> zHQF4gxeezlnpaN8%+f&BS?2d;EB$=6AI0~-ZX<76MOC-*dEKq*t{h#%7M5<!T0I`cXb0>+vRLF!uqjDe+~Xbx0##T~6C*?^LNgQDX5I YDa=1_+F1VKqe{MHoxX653|`}T;-FZaNN6rDgZ+$ z_rx#*dL}Rn;E**jB%q0T67#_ef%_yDgX@9&B_0G54H8Sh4^6;Q{9g7SNW$I(tdLj< zP6I8#{sU*;Wq*uOtum-2!CQfi!@pdmo%&wbd%?rNCTXXp{UG=xuvyxvX+IAh1M0fV z?d*qoKJ2%^Q>H7*7U@q-{{*-h*edPRw66#60d9VrdWro|(_RE004~3{eRq&5MUsANtPCXa)Kft$vrW@&M+NtNjo~TxoMxgh|x%X+Oro9Wi z1xQ%?hH0ms1$!^}dEo6&x1XV%dLHaW@GHO-@6boIQ*+!S;IqJ~FJ`<uDm!&^7*LxCt0Wi1kUPn7M^JEPC8_=7)beMK(&NG3;Zv?!(U-!^X z?Z*wg1$gt$51-IZ&H49&i@@opkM5(LdMfP0;8Eb#w}-aVPCXm;W8kyE{;l;t(@xEC zuYi97el0Zk-<_KM-vUp=Moe6D{-!@Qze@vH1v=J!xQ2G>1nldCfU<1@t#> z^6LOK=d%c$26|*a)c$@4?*X2Wc4~hzP~@E4Be3YABmVddz*!SoQrQc*bF0$+?%NEJ0Nk9U01I5^Y(!I}0C&UAEe zrYAW#aoo6`oy(a*#W7oq&3|JH@|nHMv3cU^!X5Bzc=1X0HOZD)8=u_|Q>?vF86Q6@ z+`Lwl2@%H8lzz6WtGeN8eWuWZ{@sFYBaKYW^;BdB(<^KzUMOiMN;9YEf2^V-1`WI9 zs{O8QMUmX5<;3nDd&ok0p|6}X=hJT+R@h&E<$Q7HFp7wLFy+usNI%(6&N2MyhVOHg z{Sw zWn(AG1%%@kWqZT=-whl3NBebyuH!1i7|Q;+wj9zTh!IB*+m$QV*QaF$a*kcH#y@W4 z*C+j4ZBVG1tGa_s^XR=C-$m8p(2mgU$=;91_p#5L(c<|$Ajg@kP{qfK7p4XAjL|h0 zn=D)(;&0eG&L+-EM|OImB2hU>JU#v!WxDScp)xHL;3IyisKZ6D9Unx_tg}@yJQ1U155=_`f&%r83!x_>VOr13N!<}ck+&Y z7ZATc&%tjTWBy&3_j}%tdCyG(DE+v1D9Qss6Ywz52=EwQ3d{v?1@Z6A2{7-}D}hG< znMMxEzGkrm#S$ruizC+?#KbU7G>zh+$@j=CCPvt9WI{XEs3BqG^5cJ1LcbyD7v!H` zJ!1X1!Ta&!$wNJzbe>-2ctORE*K1wY>&doWI3MI6H`;h!#ZL=unselect shape (free drawing) - ["<Primary>KP_Add"] + KP_Add','plus']]]> increment the line width increment the line width - ["<Primary>KP_Subtract"] + KP_Subtract','minus','minus']]]> decrement the line width decrement the line width @@ -136,6 +136,11 @@ toggle linecap toggle linecap + + KP_Multiply','asterisk','asterisk']]]> + toggle fill rule + toggle fill rule + ["<Primary>period"] toggle dash From 9fc08f8bcef71aa3fec01a509fdfe4065406a62b Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 8 Jun 2020 22:27:58 +0200 Subject: [PATCH 18/67] dashed line: compute dash attributes from line width (by default) --- data/default.css | 11 ++++++----- draw.js | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/data/default.css b/data/default.css index 61e858f..6d952f6 100644 --- a/data/default.css +++ b/data/default.css @@ -13,8 +13,9 @@ * 0 : nonzero (winding in Cairo), 1 : evenodd * * dash: - * dash-array-on is the length of dashes (no dashes if 0, you can put 0.1 to get dots or square according to line-cap). - * dash-array-off is the length of gaps (no dashes if 0). + * By default, it is computed from the line width. + * dash-array-on is the length of dashes (put 0.1 to get dots or squares according to line-cap). + * dash-array-off is the length of gaps * * square area: * Drawing in a square area is convenient when using the extension as a vector graphics editor. By default, @@ -36,9 +37,9 @@ -drawing-line-join: 1; -drawing-line-cap: 1; -drawing-fill-rule: 0; - -drawing-dash-array-on: 5px; - -drawing-dash-array-off: 15px; - -drawing-dash-offset: 0px; + /*-drawing-dash-array-on: 5px;*/ + /*-drawing-dash-array-off: 15px;*/ + /*-drawing-dash-offset: 0px;*/ -drawing-background-color: #2e3436; -grid-overlay-gap: 10px; -grid-overlay-line-width: 0.4px; diff --git a/draw.js b/draw.js index 445ec11..4ba108a 100644 --- a/draw.js +++ b/draw.js @@ -160,7 +160,7 @@ var DrawingArea = new Lang.Class({ this.currentLineJoin = themeNode.get_double('-drawing-line-join'); this.currentLineCap = themeNode.get_double('-drawing-line-cap'); this.currentFillRule = themeNode.get_double('-drawing-fill-rule'); - this.dashArray = [themeNode.get_length('-drawing-dash-array-on'), themeNode.get_length('-drawing-dash-array-off')]; + this.dashArray = [Math.abs(themeNode.get_length('-drawing-dash-array-on')), Math.abs(themeNode.get_length('-drawing-dash-array-off'))]; this.dashOffset = themeNode.get_length('-drawing-dash-offset'); let font = themeNode.get_font(); this.fontFamily = font.get_family(); @@ -388,7 +388,7 @@ var DrawingArea = new Lang.Class({ shape: this.currentShape, color: this.currentColor.to_string(), line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap }, - dash: { array: this.dashedLine ? this.dashArray : [0, 0] , offset: this.dashedLine ? this.dashOffset : 0 }, + dash: { active: this.dashedLine, array: this.dashedLine ? [this.dashArray[0] || this.currentLineWidth, this.dashArray[1] || this.currentLineWidth * 3] : [0, 0] , offset: this.dashOffset }, fill: this.fill, fillRule: this.currentFillRule, eraser: eraser, @@ -400,7 +400,7 @@ var DrawingArea = new Lang.Class({ if (this.currentShape == Shapes.TEXT) { this.currentElement.line = { lineWidth: 2, lineJoin: 0, lineCap: 0 }; - this.currentElement.dash = { array: [1, 2] , offset: 0 }; + this.currentElement.dash = { active: true, array: [1, 2] , offset: 0 }; this.currentElement.fill = false; this.currentElement.text = _("Text"); this.currentElement.state = TextState.DRAWING; @@ -961,7 +961,7 @@ const DrawingElement = new Lang.Class({ cr.setLineWidth(this.line.lineWidth); cr.setFillRule(this.fillRule); - if (this.dash.array[0] > 0 && this.dash.array[1] > 0) + if (this.dash.active) cr.setDash(this.dash.array, this.dash.offset); else cr.setDash([1000000], 0); @@ -1043,7 +1043,7 @@ const DrawingElement = new Lang.Class({ `stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}" ` + `stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`; - if (this.dash.array[0] > 0 && this.dash.array[1] > 0) + if (this.dash.active) attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; if (this.shape == Shapes.LINE && points.length == 3) { From 9f4e8a25d08dfe802e92a61ce63ab2cc8a61791f Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 12 Jun 2020 03:45:51 +0200 Subject: [PATCH 19/67] refactor the code before introducing new tools All geometric stuff is delegate to elements. Use general and reusable transformations. --- draw.js | 379 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 238 insertions(+), 141 deletions(-) diff --git a/draw.js b/draw.js index 4ba108a..9700567 100644 --- a/draw.js +++ b/draw.js @@ -61,6 +61,7 @@ const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_pat var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; const TextState = { DRAWING: 0, WRITING: 1 }; +const Transformations = { TRANSLATION: 0, ROTATION: 1 }; const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; @@ -144,6 +145,14 @@ var DrawingArea = new Lang.Class({ return this._menu; }, + get currentShape() { + return this._currentShape; + }, + + set currentShape(shape) { + this._currentShape = shape; + }, + _redisplay: function() { // force area to emit 'repaint' this.queue_repaint(); @@ -203,17 +212,21 @@ var DrawingArea = new Lang.Class({ 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]); + this.elements[i].buildCairo(cr, false); + + if (this.transformation) + this._findTransformingElement(cr, this.elements[i]); + if (this.elements[i].fill && !isStraightLine) { - // first paint stroke - this.elements[i].buildCairo(cr, false); + let pathCopy = cr.copyPath(); if (this.elements[i].shape == Shapes.NONE || this.elements[i].shape == Shapes.LINE) cr.closePath(); + // first paint stroke cr.stroke(); // secondly paint fill - this.elements[i].buildCairo(cr, false); + cr.appendPath(pathCopy); cr.fill(); } else { - this.elements[i].buildCairo(cr, false); cr.stroke(); } } @@ -261,7 +274,7 @@ var DrawingArea = new Lang.Class({ let [x, y] = event.get_coords(); let shiftPressed = event.has_shift_modifier(); - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { // finish writing this._stopWriting(); } @@ -309,7 +322,7 @@ var DrawingArea = new Lang.Class({ }, _onKeyPressed: function(actor, event) { - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { if (event.get_key_symbol() == Clutter.KEY_Escape) { // finish writing this._stopWriting(); @@ -395,7 +408,7 @@ var DrawingArea = new Lang.Class({ 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]] + points: [] }); if (this.currentShape == Shapes.TEXT) { @@ -403,13 +416,13 @@ var DrawingArea = new Lang.Class({ this.currentElement.dash = { active: true, array: [1, 2] , offset: 0 }; this.currentElement.fill = false; this.currentElement.text = _("Text"); - this.currentElement.state = TextState.DRAWING; + this.currentElement.textState = TextState.DRAWING; } - if (this.currentShape == Shapes.POLYGON || this.currentShape == Shapes.POLYLINE) { - this.currentElement.points.push([startX, startY]); + this.currentElement.startDrawing(startX, startY); + + if (this.currentShape == Shapes.POLYGON || this.currentShape == Shapes.POLYLINE) this.emit('show-osd', null, _("Press %s\nto mark vertices").format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); - } this.motionHandler = this.connect('motion-event', (actor, event) => { if (this.spaceKeyPressed) @@ -424,6 +437,16 @@ var DrawingArea = new Lang.Class({ }); }, + _updateDrawing: function(x, y, controlPressed) { + if (!this.currentElement) + return; + + this.currentElement.updateDrawing(x, y, controlPressed); + + this._redisplay(); + this.updatePointerCursor(controlPressed); + }, + _stopDrawing: function() { if (this.motionHandler) { this.disconnect(this.motionHandler); @@ -438,18 +461,13 @@ var DrawingArea = new Lang.Class({ if (this.currentElement && this.currentElement.shape == Shapes.POLYGON && this.currentElement.points.length < 3) this.currentElement = null; - // skip when the size is too small to be visible (3px) (except for free drawing) - if (this.currentElement && this.currentElement.shape != Shapes.NONE && this.currentElement.points.length >= 2) { - let lastPoint = this.currentElement.points[this.currentElement.points.length - 1]; - let secondToLastPoint = this.currentElement.points[this.currentElement.points.length - 2]; - if (getNearness(secondToLastPoint, lastPoint, 3)) - this.currentElement.points.pop(); - } + if (this.currentElement) + this.currentElement.stopDrawing(); if (this.currentElement && this.currentElement.points.length >= 2) { - if (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.DRAWING) { + if (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.DRAWING) { // start writing - this.currentElement.state = TextState.WRITING; + this.currentElement.textState = TextState.WRITING; this.currentElement.text = ''; this.emit('show-osd', null, _("Type your text\nand press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); this._updateTextCursorTimeout(); @@ -467,28 +485,6 @@ var DrawingArea = new Lang.Class({ this.updatePointerCursor(); }, - _updateDrawing: function(x, y, controlPressed) { - if (!this.currentElement) - return; - if (this.currentElement.shape == Shapes.NONE) - this.currentElement.addPoint(x, y, controlPressed); - else if ((this.currentElement.shape == Shapes.RECTANGLE || this.currentElement.shape == Shapes.TEXT) && (controlPressed || this.currentElement.transform.active)) - this.currentElement.transformPolygon(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 if ((this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && (controlPressed || this.currentElement.transform.active)) - this.currentElement.transformPolygon(x, y); - else if (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) - this.currentElement.points[this.currentElement.points.length - 1] = [x, y]; - else - this.currentElement.points[1] = [x, y]; - - this._redisplay(); - this.updatePointerCursor(controlPressed); - }, - _stopWriting: function(startNewLine) { if (this.currentElement.text.length > 0) this.elements.push(this.currentElement); @@ -759,7 +755,7 @@ var DrawingArea = new Lang.Class({ saveAsSvg: function() { // stop drawing or writing - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { this._stopWriting(); } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { this._stopDrawing(); @@ -797,7 +793,7 @@ var DrawingArea = new Lang.Class({ _saveAsJson: function(name, notify) { // stop drawing or writing - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { this._stopWriting(); } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { this._stopDrawing(); @@ -934,8 +930,23 @@ const DrawingElement = new Lang.Class({ this[key] = params[key]; // compatibility with json generated by old extension versions + if (params.fillRule === undefined) this.fillRule = Cairo.FillRule.WINDING; + if (params.transformations === undefined) + this.transformations = []; + + if (params.transform && params.transform.center) { + let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0); + if (angle) + this.transformations.push({ type: Transformations.ROTATION, center: params.transform.center, angle: angle }); + } + if (params.shape == Shapes.ELLIPSE && params.transform && params.transform.ratio && params.transform.ratio != 1 && params.points.length >= 2) { + let [ratio, p0, p1] = [params.transform.ratio, params.points[0], params.points[1]]; + // Add a fake point that will give the right ellipse ratio when building the element. + this.points.push([ratio * (p1[0] - p0[0]) + p0[0], ratio * (p1[1] - p0[1]) + p0[1]]); + } + delete this.transform; }, // toJSON is called by JSON.stringify @@ -948,7 +959,7 @@ const DrawingElement = new Lang.Class({ fill: this.fill, fillRule: this.fillRule, eraser: this.eraser, - transform: this.transform, + transformations: this.transformations, text: this.text, font: this.font, points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) @@ -975,7 +986,14 @@ const DrawingElement = new Lang.Class({ if (success) Clutter.cairo_set_source_color(cr, color); - let [points, shape, trans] = [this.points, this.shape, this.transform]; + this.transformations.slice(0).reverse().forEach(transformation => { + if (transformation.type == Transformations.TRANSLATION) + cr.translate(transformation.slideX, transformation.slideY); + else if (transformation.type == Transformations.ROTATION) + this._rotate(cr, transformation.angle, transformation.center[0], transformation.center[1]); + }); + + let [points, shape] = [this.points, this.shape]; if (shape == Shapes.LINE && points.length == 3) { cr.moveTo(points[0][0], points[0][1]); @@ -987,40 +1005,43 @@ const DrawingElement = new Lang.Class({ 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]); + } else if (shape == Shapes.ELLIPSE && points.length >= 2) { + let ratio = 1; + if (points[2]) + ratio = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]) / Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]); + + this._scale(cr, ratio, 1, points[0][0], points[0][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]); + this._scale(cr, 1 / ratio, 1, points[0][0], points[0][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.POLYGON || shape == Shapes.POLYLINE) && points.length >= 2) { - this.rotate(cr, trans.angle, trans.center[0], trans.center[1]); cr.moveTo(points[0][0], points[0][1]); for (let j = 1; j < points.length; j++) { cr.lineTo(points[j][0], points[j][1]); } if (shape == Shapes.POLYGON) cr.closePath(); - 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]); - // the state property is undefined if the element is loaded from json file - if (this.state !== undefined && this.state == TextState.DRAWING) + // the textState property is undefined if the element is loaded from json file + if (this.textState !== undefined && this.textState == TextState.DRAWING) cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][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]); } + + this.transformations.forEach(transformation => { + if (transformation.type == Transformations.TRANSLATION) + cr.translate(-transformation.slideX, -transformation.slideY); + else if (transformation.type == Transformations.ROTATION) + this._rotate(cr, -transformation.angle, transformation.center[0], transformation.center[1]); + }); }, buildSVG: function(bgColor) { @@ -1046,67 +1067,60 @@ const DrawingElement = new Lang.Class({ if (this.dash.active) attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; + let transAttribute = ''; + this.transformations.filter(transformation => transformation.type == Transformations.TRANSLATION) + .forEach(transformation => { + transAttribute += transAttribute ? ' ' : ' transform="'; + transAttribute += `translate(${transformation.slideX},${transformation.slideY})`; + }); + this.transformations.filter(transformation => transformation.type == Transformations.ROTATION) + .forEach(transformation => { + transAttribute += transAttribute ? ' ' : ' transform="'; + transAttribute += `rotate(${transformation.angle * 180 / Math.PI},` + + `${transformation.center[0] - transformation.totalSlide[0]},${transformation.center[1] - transformation.totalSlide[1]})`; + }); + transAttribute += transAttribute ? '"' : ''; + if (this.shape == Shapes.LINE && points.length == 3) { row += ``; + row += `${fill ? 'z' : ''}"${transAttribute}/>`; } else if (this.shape == Shapes.LINE) { - row += ``; + row += ``; } else if (this.shape == Shapes.NONE) { row += ``; + row += `${fill ? 'z' : ''}"${transAttribute}/>`; - } else if (this.shape == Shapes.ELLIPSE && points.length == 2 && this.transform.ratio != 1) { + } else if (this.shape == Shapes.ELLIPSE && points.length == 3) { 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 += ``; + let rx = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]); + row += ``; } 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 += ``; + row += ``; } 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 += ``; } else if (this.shape == Shapes.POLYGON && points.length >= 3) { - 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 += ``; } else if (this.shape == Shapes.POLYLINE && 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 += ``; } 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" ` + @@ -1115,16 +1129,17 @@ const DrawingElement = new Lang.Class({ `font-weight="${FontWeightNames[this.font.weight].toLowerCase()}" ` + `font-style="${FontStyleNames[this.font.style].toLowerCase()}"`; - row += `${this.text}`; + row += `${this.text}`; } return row; }, - addPoint: function(x, y, smoothedStroke) { - this.points.push([x, y]); - if (smoothedStroke) - this.smooth(this.points.length - 1); + get lastTransformation() { + if (!this.transformations.length) + return null; + + return this.transformations[this.transformations.length - 1]; }, smooth: function(i) { @@ -1139,7 +1154,87 @@ const DrawingElement = new Lang.Class({ } }, - rotate: function(cr, angle, x, y) { + startDrawing: function(startX, startY) { + this.points.push([startX, startY]); + + if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) + this.points.push([startX, startY]); + }, + + updateDrawing: function(x, y, transform) { + let points = this.points; + if (x == points[points.length - 1][0] && y == points[points.length - 1][1]) + return; + + transform = transform || this.transformations.length >= 1 || this.shape == Shapes.LINE && points.length == 3; + + if (this.shape == Shapes.NONE) { + this._addPoint(x, y, transform); + + } else if ((this.shape == Shapes.RECTANGLE || this.shape == Shapes.TEXT || this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) && transform) { + if (points.length < 2) + return; + + if (!this.transformations[0]) + this.transformations[0] = { type: Transformations.ROTATION, center: this._getCenter() }; + this.transformations[0].angle = getAngle(this.transformations[0].center[0], this.transformations[0].center[1], + points[points.length - 1][0], points[points.length - 1][1], + x, y); + + } else if (this.shape == Shapes.ELLIPSE && transform) { + if (points.length < 2) + return; + + points[2] = [x, y]; + + if (!this.transformations[0]) + this.transformations[0] = { type: Transformations.ROTATION, center: this._getCenter() }; + this.transformations[0].angle = getAngle(this.transformations[0].center[0], this.transformations[0].center[1], + this.transformations[0].center[0] + 1, this.transformations[0].center[1], + x, y); + + } else if (this.shape == Shapes.LINE && transform) { + if (points.length < 2) + return; + + if (points.length == 2) + points[2] = points[1]; + points[1] = [x, y]; + + } else if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) { + points[points.length - 1] = [x, y]; + + } else { + points[1] = [x, y]; + + } + }, + + stopDrawing: function() { + // skip when the size is too small to be visible (3px) (except for free drawing) + if (this.shape != Shapes.NONE && this.points.length >= 2) { + let lastPoint = this.points[this.points.length - 1]; + let secondToLastPoint = this.points[this.points.length - 2]; + if (getNearness(secondToLastPoint, lastPoint, 3)) + this.points.pop(); + } + }, + + // The figure center before transformations + _getCenter: function() { + return this.shape == Shapes.ELLIPSE ? [this.points[0][0], this.points[0][1]] : + this.shape == Shapes.LINE && this.points.length == 3 ? getCurveCenter(this.points[0], this.points[1], this.points[2]) : + this.points.length >= 3 ? getCentroid(this.points) : + getNaiveCenter(this.points); + }, + + _addPoint: function(x, y, smoothedStroke) { + this.points.push([x, y]); + if (smoothedStroke) + this.smooth(this.points.length - 1); + }, + + _rotate: function(cr, angle, x, y) { if (angle == 0) return; cr.translate(x, y); @@ -1147,49 +1242,12 @@ const DrawingElement = new Lang.Class({ cr.translate(-x, -y); }, - scale: function(cr, ratio, x, y) { - if (ratio == 1) + _scale: function(cr, scaleX, scaleY, x, y) { + if (scaleX == scaleY) return; cr.translate(x, y); - cr.scale(ratio, 1); + cr.scale(scaleX, scaleY); cr.translate(-x, -y); - }, - - transformPolygon: 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.length >= 3 ? getCentroid(points) : getNaiveCenter(points); - this.transform.angle = getAngle(this.transform.center[0], this.transform.center[1], points[points.length - 1][0], points[points.length - 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; } }); @@ -1217,22 +1275,61 @@ const getCentroid = function(points) { } points.pop(); + if (sA == 0) + return getNaiveCenter(points); return [sX / (3 * sA), sY / (3 * sA)]; }; +/* +Cubic Bézier: +[0, 1] -> ℝ², P(t) = (1-t)³P₀ + 3t(1-t)²P₁ + 3t²(1-t)P₂ + t³P₃ + +general case: + +const cubicBezierCoord = function(x0, x1, x2, x3, t) { + return (1-t)**3*x0 + 3*t*(1-t)**2*x1 + 3*t**2*(1-t)*x2 + t**3*x3; +} + +const cubicBezierPoint = function(p0, p1, p2, p3, t) { + return [cubicBezier(p0[0], p1[0], p2[0], p3[0], t), cubicBezier(p0[1], p1[1], p2[1], p3[1], t)]; +} + +Approximatively: +control point: p0 ---- p1 ---- p2 ---- p3 (p2 is not on the curve) + t: 0 ---- 1/3 ---- 2/3 ---- 1 +*/ + +// Here, p0 = p1 and t = 2/3. +// If the curve has a symmetry axis, p(2/3) is truly a center (the intersection of the curve and the axis). +// In other cases, it is not a notable point, just a visual approximation. +const getCurveCenter = function(p1, p2, p3) { + return [(p1[0] + 6*p1[0] + 12*p2[0] + 8*p3[0]) / 27, (p1[1] + 6*p1[1] + 12*p2[1] + 8*p3[1]) / 27]; +}; + const getAngle = function(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)) ); + let cos = ((xA - xO)*(xB - xO) + (yA - yO)*(yB - yO)) / (Math.hypot(xA - xO, yA - yO) * Math.hypot(xB - xO, yB - yO)); + + // acos is defined on [-1, 1] but + // with A == B and imperfect computer calculations, cos may be equal to 1.00000001. + cos = Math.min(Math.max(-1, cos), 1); + let angle = Math.acos( cos ); // 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; + if (xA == xO) { + if (xB > xO) + angle = -angle; + } else { + // 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; }; From 443d1f8662227233dcadd398ae8b205197bfb35c Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 13 Jun 2020 13:53:52 +0200 Subject: [PATCH 20/67] Introduce move manipulation * 'Shapes' -> 'Tools' ('Shapes' + 'Manipulations') * 'TextState' -> 'TextStates' * Rotation centers are now computed dynamically (when building Cairo or SVG paths). * Text rotation center is now located at the beginning of the text. * Text elements gain a 'lineIndex' attribute to retrieve the rotation center of the first line. --- draw.js | 341 ++++++++++++++---- extension.js | 15 +- locale/draw-on-your-screen.pot | 16 +- prefs.js | 7 +- schemas/gschemas.compiled | Bin 3920 -> 3972 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 + 6 files changed, 306 insertions(+), 78 deletions(-) diff --git a/draw.js b/draw.js index 9700567..4b1be8c 100644 --- a/draw.js +++ b/draw.js @@ -59,10 +59,12 @@ const FILLRULE_EVENODD_ICON_PATH = ICON_DIR.get_child('fillrule-evenodd-symbolic const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get_path(); const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path(); -var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; -const TextState = { DRAWING: 0, WRITING: 1 }; +const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; +const Manipulations = { MOVE: 100 }; +var Tools = Object.assign({}, Shapes, Manipulations); +const TextStates = { WRITTEN: 0, DRAWING: 1, WRITING: 2 }; const Transformations = { TRANSLATION: 0, ROTATION: 1 }; -const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline" }; +const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; @@ -126,7 +128,7 @@ var DrawingArea = new Lang.Class({ this.elements = []; this.undoneElements = []; this.currentElement = null; - this.currentShape = Shapes.NONE; + this.currentTool = Shapes.NONE; this.isSquareArea = false; this.hasGrid = false; this.hasBackground = false; @@ -145,12 +147,16 @@ var DrawingArea = new Lang.Class({ return this._menu; }, - get currentShape() { - return this._currentShape; + get currentTool() { + return this._currentTool; }, - set currentShape(shape) { - this._currentShape = shape; + set currentTool(tool) { + this._currentTool = tool; + if (Object.values(Manipulations).indexOf(tool) != -1) + this._startElementFinder(); + else + this._stopElementFinder(); }, _redisplay: function() { @@ -210,29 +216,31 @@ var DrawingArea = new Lang.Class({ for (let i = 0; i < this.elements.length; i++) { 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]); + (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]); - this.elements[i].buildCairo(cr, false); + let showTextRectangle = this.elements[i].shape == Shapes.TEXT && this.transformingElement && this.transformingElement == this.elements[i]; + this.elements[i].buildCairo(cr, false, showTextRectangle); - if (this.transformation) + if (this.finderPoint) this._findTransformingElement(cr, this.elements[i]); if (this.elements[i].fill && !isStraightLine) { let pathCopy = cr.copyPath(); if (this.elements[i].shape == Shapes.NONE || this.elements[i].shape == Shapes.LINE) cr.closePath(); - // first paint stroke - cr.stroke(); - // secondly paint fill + cr.stroke(); // first paint stroke cr.appendPath(pathCopy); - cr.fill(); + cr.fill(); // secondly paint fill } else { cr.stroke(); } } if (this.currentElement) { - this.currentElement.buildCairo(cr, this.textHasCursor); + let showTextRectangle = this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING; + this.currentElement.buildCairo(cr, this.textHasCursor, showTextRectangle); if (this.currentElement.fill && this.currentElement.line.lineWidth == 0) { // add a dummy stroke while drawing @@ -274,7 +282,7 @@ var DrawingArea = new Lang.Class({ let [x, y] = event.get_coords(); let shiftPressed = event.has_shift_modifier(); - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { // finish writing this._stopWriting(); } @@ -286,7 +294,12 @@ var DrawingArea = new Lang.Class({ } if (button == 1) { - this._startDrawing(x, y, shiftPressed); + if (this.currentTool == Manipulations.MOVE) { + if (this.elements.length) + this._startTransforming(x, y, shiftPressed); + } else { + this._startDrawing(x, y, shiftPressed); + } return Clutter.EVENT_STOP; } else if (button == 2) { this.toggleFill(); @@ -322,7 +335,7 @@ var DrawingArea = new Lang.Class({ }, _onKeyPressed: function(actor, event) { - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { if (event.get_key_symbol() == Clutter.KEY_Escape) { // finish writing this._stopWriting(); @@ -387,6 +400,111 @@ var DrawingArea = new Lang.Class({ return Clutter.EVENT_STOP; }, + _findTransformingElement: function(cr, element) { + if (element.getContainsPoint(cr, this.finderPoint[0], this.finderPoint[1])) + this.transformingElement = element; + else if (this.transformingElement == element) + this.transformingElement = null; + + if (element == this.elements[this.elements.length - 1]) { + // All elements have been tested, the winner is the last. + this.updatePointerCursor(); + } + }, + + _startElementFinder: function() { + this.elementFinderHandler = this.connect('motion-event', (actor, event) => { + if (this.motionHandler) { + this.finderPoint = null; + return; + } + + let coords = event.get_coords(); + let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); + if (!s) + return; + + this.finderPoint = [x, y]; + this.transformingElement = null; + // this._redisplay calls this._findTransformingElement. + this._redisplay(); + }); + }, + + _stopElementFinder: function() { + if (this.elementFinderHandler) { + this.disconnect(this.elementFinderHandler); + this.finderPoint = null; + this.elementFinderHandler = null; + } + }, + + _startTransforming: function(stageX, stageY, duplicate) { + if (!this.transformingElement) + return; + + let [success, startX, startY] = this.transform_stage_point(stageX, stageY); + + if (!success) + return; + + this.buttonReleasedHandler = this.connect('button-release-event', (actor, event) => { + this._stopTransforming(); + }); + + if (duplicate) { + // deep cloning + let copy = new DrawingElement(JSON.parse(JSON.stringify(this.transformingElement))); + this.elements.push(copy); + this.transformingElement = copy; + } + + if (this.currentTool == Manipulations.MOVE) + this.transformingElement.startTransformation(startX, startY, Transformations.TRANSLATION); + + this.finderPoint = null; + + this.motionHandler = this.connect('motion-event', (actor, event) => { + if (this.spaceKeyPressed) + return; + + let coords = event.get_coords(); + let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); + if (!s) + return; + let controlPressed = event.has_control_modifier(); + this._updateTransforming(x, y, controlPressed); + }); + }, + + _updateTransforming: function(x, y, controlPressed) { + if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.TRANSLATION) { + this.transformingElement.stopTransformation(x, y); + this.transformingElement.startTransformation(x, y, Transformations.ROTATION); + } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.ROTATION) { + this.transformingElement.stopTransformation(x, y); + this.transformingElement.startTransformation(x, y, Transformations.TRANSLATION); + } + + this.transformingElement.updateTransformation(x, y); + this._redisplay(); + }, + + _stopTransforming: function() { + if (this.motionHandler) { + this.disconnect(this.motionHandler); + this.motionHandler = null; + } + if (this.buttonReleasedHandler) { + this.disconnect(this.buttonReleasedHandler); + this.buttonReleasedHandler = null; + } + + this.transformingElement.stopTransformation(); + this.transformingElement = null; + this._redisplay(); + }, + _startDrawing: function(stageX, stageY, eraser) { let [success, startX, startY] = this.transform_stage_point(stageX, stageY); @@ -398,7 +516,7 @@ var DrawingArea = new Lang.Class({ }); this.currentElement = new DrawingElement ({ - shape: this.currentShape, + shape: this.currentTool, color: this.currentColor.to_string(), line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap }, dash: { active: this.dashedLine, array: this.dashedLine ? [this.dashArray[0] || this.currentLineWidth, this.dashArray[1] || this.currentLineWidth * 3] : [0, 0] , offset: this.dashOffset }, @@ -411,17 +529,17 @@ var DrawingArea = new Lang.Class({ points: [] }); - if (this.currentShape == Shapes.TEXT) { + if (this.currentTool == Shapes.TEXT) { this.currentElement.line = { lineWidth: 2, lineJoin: 0, lineCap: 0 }; this.currentElement.dash = { active: true, array: [1, 2] , offset: 0 }; this.currentElement.fill = false; this.currentElement.text = _("Text"); - this.currentElement.textState = TextState.DRAWING; + this.currentElement.textState = TextStates.DRAWING; } this.currentElement.startDrawing(startX, startY); - if (this.currentShape == Shapes.POLYGON || this.currentShape == Shapes.POLYLINE) + if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE) this.emit('show-osd', null, _("Press %s\nto mark vertices").format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); this.motionHandler = this.connect('motion-event', (actor, event) => { @@ -465,9 +583,9 @@ var DrawingArea = new Lang.Class({ this.currentElement.stopDrawing(); if (this.currentElement && this.currentElement.points.length >= 2) { - if (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.DRAWING) { + if (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING) { // start writing - this.currentElement.textState = TextState.WRITING; + this.currentElement.textState = TextStates.WRITING; this.currentElement.text = ''; this.emit('show-osd', null, _("Type your text\nand press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); this._updateTextCursorTimeout(); @@ -486,11 +604,16 @@ var DrawingArea = new Lang.Class({ }, _stopWriting: function(startNewLine) { + this.currentElement.textState = TextStates.WRITTEN; + if (this.currentElement.text.length > 0) this.elements.push(this.currentElement); if (startNewLine && this.currentElement.points.length == 2) { + this.currentElement.lineIndex = this.currentElement.lineIndex || 0; // copy object, the original keep existing in this.elements this.currentElement = Object.create(this.currentElement); + this.currentElement.textState = TextStates.WRITING; + this.currentElement.lineIndex ++; let height = Math.abs(this.currentElement.points[1][1] - this.currentElement.points[0][1]); // define a new 'points' array, the original keep existing in this.elements this.currentElement.points = [ @@ -513,8 +636,10 @@ var DrawingArea = new Lang.Class({ }, updatePointerCursor: function(controlPressed) { - if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING)) - this.setPointerCursor(this.currentShape == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); + if (this.currentTool == Manipulations.MOVE) + this.setPointerCursor(this.transformingElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); + else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING)) + this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); else if (this.currentElement.shape != Shapes.NONE && controlPressed) this.setPointerCursor('MOVE_OR_RESIZE_WINDOW'); }, @@ -619,9 +744,9 @@ var DrawingArea = new Lang.Class({ this.emit('show-osd', null, this.currentColor.to_string(), this.currentColor.to_string().slice(0, 7), -1); }, - selectShape: function(shape) { - this.currentShape = shape; - this.emit('show-osd', null, _(ShapeNames[shape]), "", -1); + selectTool: function(tool) { + this.currentTool = tool; + this.emit('show-osd', null, _(ToolNames[tool]), "", -1); this.updatePointerCursor(); }, @@ -724,6 +849,10 @@ var DrawingArea = new Lang.Class({ this.disconnect(this._onKeyboardPopupMenuHandler); this._onKeyboardPopupMenuHandler = null; } + if (this.elementFinderHandler) { + this.disconnect(this.elementFinderHandler); + this.elementFinderHandler = null; + } if (this.motionHandler) { this.disconnect(this.motionHandler); this.motionHandler = null; @@ -742,7 +871,7 @@ var DrawingArea = new Lang.Class({ this.currentElement = null; this._stopTextCursorTimeout(); - this.currentShape = Shapes.NONE; + this.currentTool = Shapes.NONE; this.dashedLine = false; this.fill = false; this._redisplay(); @@ -755,7 +884,7 @@ var DrawingArea = new Lang.Class({ saveAsSvg: function() { // stop drawing or writing - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { this._stopWriting(); } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { this._stopDrawing(); @@ -793,7 +922,7 @@ var DrawingArea = new Lang.Class({ _saveAsJson: function(name, notify) { // stop drawing or writing - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextState.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { this._stopWriting(); } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { this._stopDrawing(); @@ -939,7 +1068,7 @@ const DrawingElement = new Lang.Class({ if (params.transform && params.transform.center) { let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0); if (angle) - this.transformations.push({ type: Transformations.ROTATION, center: params.transform.center, angle: angle }); + this.transformations.push({ type: Transformations.ROTATION, angle: angle }); } if (params.shape == Shapes.ELLIPSE && params.transform && params.transform.ratio && params.transform.ratio != 1 && params.points.length >= 2) { let [ratio, p0, p1] = [params.transform.ratio, params.points[0], params.points[1]]; @@ -961,12 +1090,13 @@ const DrawingElement = new Lang.Class({ eraser: this.eraser, transformations: this.transformations, text: this.text, + lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined, font: this.font, points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) }; }, - buildCairo: function(cr, showTextCursor) { + buildCairo: function(cr, showTextCursor, showTextRectangle) { cr.setLineCap(this.line.lineCap); cr.setLineJoin(this.line.lineJoin); cr.setLineWidth(this.line.lineWidth); @@ -987,10 +1117,12 @@ const DrawingElement = new Lang.Class({ Clutter.cairo_set_source_color(cr, color); this.transformations.slice(0).reverse().forEach(transformation => { - if (transformation.type == Transformations.TRANSLATION) + if (transformation.type == Transformations.TRANSLATION) { cr.translate(transformation.slideX, transformation.slideY); - else if (transformation.type == Transformations.ROTATION) - this._rotate(cr, transformation.angle, transformation.center[0], transformation.center[1]); + } else if (transformation.type == Transformations.ROTATION) { + let centerWithSlide = this._getCenterWithSlide(transformation); + this._rotate(cr, transformation.angle, centerWithSlide[0], centerWithSlide[1]); + } }); let [points, shape] = [this.points, this.shape]; @@ -1027,23 +1159,49 @@ const DrawingElement = new Lang.Class({ cr.closePath(); } else if (shape == Shapes.TEXT && points.length == 2) { - // the textState property is undefined if the element is loaded from json file - if (this.textState !== undefined && this.textState == TextState.DRAWING) - cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][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); + + if (!showTextCursor) + this.textWidth = cr.getCurrentPoint()[0] - Math.min(points[0][0], points[1][0]) + + cr.rectangle(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]), + this.textWidth, - Math.abs(points[1][1] - points[0][1])); + + if (!showTextRectangle) + cr.setLineWidth(0); } this.transformations.forEach(transformation => { - if (transformation.type == Transformations.TRANSLATION) + if (transformation.type == Transformations.TRANSLATION) { cr.translate(-transformation.slideX, -transformation.slideY); - else if (transformation.type == Transformations.ROTATION) - this._rotate(cr, -transformation.angle, transformation.center[0], transformation.center[1]); + } else if (transformation.type == Transformations.ROTATION) { + let centerWithSlide = this._getCenterWithSlide(transformation); + this._rotate(cr, -transformation.angle, centerWithSlide[0], centerWithSlide[1]); + } }); }, + getContainsPoint: function(cr, x, y) { + if (this.shape == Shapes.TEXT) + return cr.inFill(x, y); + + cr.setLineWidth(Math.max(this.line.lineWidth, 25)); + cr.setDash([], 0); + + // Check whether the point is inside/on/near the element. + let inElement = cr.inStroke(x, y) || this.fill && cr.inFill(x, y); + + cr.setLineWidth(this.line.lineWidth); + if (this.dash.active) + cr.setDash(this.dash.array, this.dash.offset); + else + cr.setDash([1000000], 0); + return inElement; + }, + buildSVG: function(bgColor) { let row = "\n "; let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]); @@ -1076,8 +1234,8 @@ const DrawingElement = new Lang.Class({ this.transformations.filter(transformation => transformation.type == Transformations.ROTATION) .forEach(transformation => { transAttribute += transAttribute ? ' ' : ' transform="'; - transAttribute += `rotate(${transformation.angle * 180 / Math.PI},` + - `${transformation.center[0] - transformation.totalSlide[0]},${transformation.center[1] - transformation.totalSlide[1]})`; + let center = this._getCenter(); + transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; }); transAttribute += transAttribute ? '"' : ''; @@ -1175,23 +1333,16 @@ const DrawingElement = new Lang.Class({ if (points.length < 2) return; - if (!this.transformations[0]) - this.transformations[0] = { type: Transformations.ROTATION, center: this._getCenter() }; - this.transformations[0].angle = getAngle(this.transformations[0].center[0], this.transformations[0].center[1], - points[points.length - 1][0], points[points.length - 1][1], - x, y); + let center = this._getCenter(); + this.transformations[0] = { type: Transformations.ROTATION, angle: getAngle(center[0], center[1], points[points.length - 1][0], points[points.length - 1][1], x, y) }; } else if (this.shape == Shapes.ELLIPSE && transform) { if (points.length < 2) return; points[2] = [x, y]; - - if (!this.transformations[0]) - this.transformations[0] = { type: Transformations.ROTATION, center: this._getCenter() }; - this.transformations[0].angle = getAngle(this.transformations[0].center[0], this.transformations[0].center[1], - this.transformations[0].center[0] + 1, this.transformations[0].center[1], - x, y); + let center = this._getCenter(); + this.transformations[0] = { type: Transformations.ROTATION, angle: getAngle(center[0], center[1], center[0] + 1, center[1], x, y) }; } else if (this.shape == Shapes.LINE && transform) { if (points.length < 2) @@ -1220,12 +1371,68 @@ const DrawingElement = new Lang.Class({ } }, - // The figure center before transformations + startTransformation: function(startX, startY, type) { + if (type == Transformations.TRANSLATION) + this.transformations.push({ startX: startX, startY: startY, type: type, slideX: 0, slideY: 0 }); + else if (type == Transformations.ROTATION) + this.transformations.push({ startX: startX, startY: startY, type: type, angle: 0 }); + }, + + updateTransformation: function(x, y) { + let transformation = this.transformations[this.transformations.length - 1]; + + if (transformation.type == Transformations.TRANSLATION) { + transformation.slideX = x - transformation.startX; + transformation.slideY = y - transformation.startY; + } else if (transformation.type == Transformations.ROTATION) { + let centerWithSlide = this._getCenterWithSlide(transformation); + transformation.angle = getAngle(centerWithSlide[0], centerWithSlide[1], transformation.startX, transformation.startY, x, y); + } + }, + + stopTransformation: function(x, y) { + // Clean transformations + let transformation = this.transformations[this.transformations.length - 1]; + + if (transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < 1 || + transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < Math.PI / 1000) { + this.transformations.pop(); + } else { + delete transformation.startX; + delete transformation.startY; + } + }, + + _getTotalSlideBefore: function(transformation) { + let totalSlide = [0, 0]; + + this.transformations.slice(0, this.transformations.indexOf(transformation)) + .filter(transformation => transformation.type == Transformations.TRANSLATION) + .forEach(transformation => totalSlide = [totalSlide[0] + transformation.slideX, totalSlide[1] + transformation.slideY]); + + return totalSlide; + }, + + // When rotating grouped lines, lineOffset is used to retrieve the rotation center of the first line. + _getLineOffset: function() { + return (this.lineIndex || 0) * Math.abs(this.points[1][1] - this.points[0][1]); + }, + + // The figure rotation center before transformations (original). _getCenter: function() { - return this.shape == Shapes.ELLIPSE ? [this.points[0][0], this.points[0][1]] : - this.shape == Shapes.LINE && this.points.length == 3 ? getCurveCenter(this.points[0], this.points[1], this.points[2]) : - this.points.length >= 3 ? getCentroid(this.points) : - getNaiveCenter(this.points); + let points = this.points; + + return this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] : + this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[1], points[2]) : + this.shape == Shapes.TEXT && this.textWidth ? [Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]) - this._getLineOffset()] : + points.length >= 3 ? getCentroid(points) : + getNaiveCenter(points); + }, + + // The figure rotation center that takes previous translations into account. + _getCenterWithSlide: function(transformation) { + let [center, totalSlide] = [this._getCenter(), this._getTotalSlideBefore(transformation)]; + return [center[0] + totalSlide[0], center[1] + totalSlide[1]]; }, _addPoint: function(x, y, smoothedStroke) { @@ -1580,7 +1787,7 @@ const DrawingMenu = new Lang.Class({ this.menu.addAction(_("Smooth"), this.area.smoothLastElement.bind(this.area), 'format-text-strikethrough-symbolic'); this._addSeparator(this.menu); - this._addSubMenuItem(this.menu, null, ShapeNames, this.area, 'currentShape', this._updateSectionVisibility.bind(this)); + this._addSubMenuItem(this.menu, null, ToolNames, this.area, 'currentTool', this._updateSectionVisibility.bind(this)); this._addColorSubMenuItem(this.menu); this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill', this._updateSectionVisibility.bind(this)); this.fillSection = new PopupMenu.PopupMenuSection(); @@ -1628,7 +1835,7 @@ const DrawingMenu = new Lang.Class({ }, _updateSectionVisibility: function() { - if (this.area.currentShape != Shapes.TEXT) { + if (this.area.currentTool != Shapes.TEXT) { this.lineSection.actor.show(); this.fontSection.actor.hide(); this.fillItem.setSensitive(true); @@ -1740,10 +1947,10 @@ const DrawingMenu = new Lang.Class({ subItem.label.get_clutter_text().set_use_markup(true); - // change the display order of shapes - if (obj == ShapeNames && i == Shapes.POLYGON) + // change the display order of tools + if (obj == ToolNames && i == Shapes.POLYGON) item.menu.moveMenuItem(subItem, 4); - else if (obj == ShapeNames && i == Shapes.POLYLINE) + else if (obj == ToolNames && i == Shapes.POLYLINE) item.menu.moveMenuItem(subItem, 5); } return GLib.SOURCE_REMOVE; diff --git a/extension.js b/extension.js index 17bbb08..98a7f23 100644 --- a/extension.js +++ b/extension.js @@ -187,13 +187,14 @@ var AreaManager = new Lang.Class({ 'toggle-fill-rule': this.activeArea.toggleFillRule.bind(this.activeArea), 'toggle-dash' : this.activeArea.toggleDash.bind(this.activeArea), 'toggle-fill' : this.activeArea.toggleFill.bind(this.activeArea), - 'select-none-shape': () => this.activeArea.selectShape(Draw.Shapes.NONE), - 'select-line-shape': () => this.activeArea.selectShape(Draw.Shapes.LINE), - 'select-ellipse-shape': () => this.activeArea.selectShape(Draw.Shapes.ELLIPSE), - 'select-rectangle-shape': () => this.activeArea.selectShape(Draw.Shapes.RECTANGLE), - 'select-text-shape': () => this.activeArea.selectShape(Draw.Shapes.TEXT), - 'select-polygon-shape': () => this.activeArea.selectShape(Draw.Shapes.POLYGON), - 'select-polyline-shape': () => this.activeArea.selectShape(Draw.Shapes.POLYLINE), + 'select-none-shape': () => this.activeArea.selectTool(Draw.Tools.NONE), + 'select-line-shape': () => this.activeArea.selectTool(Draw.Tools.LINE), + 'select-ellipse-shape': () => this.activeArea.selectTool(Draw.Tools.ELLIPSE), + 'select-rectangle-shape': () => this.activeArea.selectTool(Draw.Tools.RECTANGLE), + 'select-text-shape': () => this.activeArea.selectTool(Draw.Tools.TEXT), + 'select-polygon-shape': () => this.activeArea.selectTool(Draw.Tools.POLYGON), + 'select-polyline-shape': () => this.activeArea.selectTool(Draw.Tools.POLYLINE), + 'select-move-tool': () => this.activeArea.selectTool(Draw.Tools.MOVE), 'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea), 'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea), 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index c221e6d..c6c205d 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -65,6 +65,9 @@ msgstr "" msgid "Polyline" msgstr "" +msgid "Move" +msgstr "" + msgid "Fill" msgstr "" @@ -158,6 +161,9 @@ msgstr "" msgid "Smooth last brushstroke" msgstr "" +msgid "Free drawing" +msgstr "" + msgid "Select line" msgstr "" @@ -176,7 +182,7 @@ msgstr "" msgid "Select text" msgstr "" -msgid "Unselect shape (free drawing)" +msgid "Select move" msgstr "" msgid "Toggle fill/stroke" @@ -270,6 +276,9 @@ msgstr "" msgid "Transform shape (when drawing)" msgstr "" +msgid "Rotate drawing (when moving)" +msgstr "" + msgid "Increment/decrement line width" msgstr "" @@ -283,7 +292,10 @@ msgstr "" msgid "%s … %s" msgstr "" -msgid "Select eraser" +msgid "Select eraser (when starting drawing)" +msgstr "" + +msgid "Duplicate drawing (when starting moving)" msgstr "" # %s is a key label diff --git a/prefs.js b/prefs.js index 9c7aaad..7150319 100644 --- a/prefs.js +++ b/prefs.js @@ -46,12 +46,13 @@ var INTERNAL_KEYBINDINGS = { 'delete-last-element' : "Erase last brushstroke", 'smooth-last-element': "Smooth last brushstroke", '-separator-1': '', + 'select-none-shape': "Free drawing", 'select-line-shape': "Select line", 'select-ellipse-shape': "Select ellipse", 'select-rectangle-shape': "Select rectangle", 'select-polygon-shape': "Select polygon", 'select-text-shape': "Select text", - 'select-none-shape': "Unselect shape (free drawing)", + 'select-move-tool': "Select move", 'toggle-fill': "Toggle fill/stroke", '-separator-2': '', 'increment-line-width': "Increment line width", @@ -94,9 +95,11 @@ var OTHER_SHORTCUTS = [ { desc: "Menu", get shortcut() { return _("Right click"); } }, { desc: "Toggle fill/stroke", get shortcut() { return _("Center click"); } }, { desc: "Transform shape (when drawing)", shortcut: getKeyLabel('') }, + { desc: "Rotate drawing (when moving)", shortcut: getKeyLabel('') }, { desc: "Increment/decrement line width", get shortcut() { return _("Scroll"); } }, { desc: "Select color", get shortcut() { return _("%s … %s").format(getKeyLabel('1'), getKeyLabel('9')); } }, - { desc: "Select eraser", get shortcut() { return _("%s held").format(getKeyLabel('')); } }, + { desc: "Select eraser (when starting drawing)", get shortcut() { return _("%s held").format(getKeyLabel('')); } }, + { desc: "Duplicate drawing (when starting moving)", get shortcut() { return _("%s held").format(getKeyLabel('')); } }, { desc: "Ignore pointer movement", get shortcut() { return _("%s held").format(getKeyLabel('space')); } }, { desc: "Leave", shortcut: getKeyLabel('Escape') } ]; diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index ce2063209e49a15b23db822e9aff504314b53201..6c557a298c87f442230762c3e457776cff4d62ff 100644 GIT binary patch literal 3972 zcmZ`+Z)jC@7(SNE<=<(}+?+K{^G-c?n{%5RHn*C(EMa395|KN*_jm6-+qvgl&pCJ7 zohCzt?2ix;B$l)Yl0wkPL`2ZO#0&yIL@9k)P+%Vt14AXup6A@(J?Gpt2M^DCp7Wmf zeSd%NyX~)Nwyv43BL9|xcjxWMBT}0Io;f+%Nank!(xn^$w?C#RCFP3p0Dof(ASf>@ zfUEEriK$mB>bq6oq-1`%$0- zm<2oz%mwBFOclmLJQuJC_F@2K`FIjG$5ja|1FC?hLRbNtct&Csm{>gpYw#QaYNlWv zo{9B96sVKf0PY<5^XhUi=W3oZq;!Kbz*ec#ralH92ewI_HuW>$tH5@t)22QN=ER)5 zaKDW4X)l7l6uca`oY0q0r%k;M+yQjSIJCWbz!~7hLOoI+1CIkG^`FmX9NJ3|=REid zutWOOW}Iu_yFk;HFI(wPy9|11DQW7h=0KJ**ld%&BYuDwQ` zb~W^BY_NKu%-laho%USl?clAzoomovz}w%FMtX6ov*3W z=6ElIuLHY}o%@VBZTjB=&zP+!Cr+=lsMDSUy$rk(sC+fA)23br?f?!PKJzjCX;(n+ z10MtyjJDQOr_H>_z$bv$n$-)`X_rGk555A_{83v)YUqa}>qs z*|UZIw3+uB_-~+W(*Bn^?I)m@;;@zjx6{?$xM;KfYH$lI&Ic-1bh`3sN3$1mp1F41m6Lkm*_DKW+N= zfyV$x7$WAXG0U**27I@<+t$;Xoojokdrw2~)S%3-P$HUB6ZYqQANJoEJZ+rLzj2Cx zW8mL7oqy95|E9pdso<}haXqHFmaWEhN9#9493Odqk^UOjN+u1VCM?rchlQR@xj}?s zEZ5WlH5ZSkIq+^SBx@#%aZ*}Fgub(g$CZXvH+up!;bI_iL9nZ@(ittG{tPHWi2^%a_Ujz5SR8jn#t z4`|Xd4Cv4IwtC!Op-qsqbuNkjJ}JMC3v_U4ffM4T8rI`(N=;if_9*{+f4MKP9ecC= zuC2vf^kIyj5w@c{=nh?#6QrBT{Fj(lIWt`AhmIp#JPz-yI&RLGnqQZG2+qE)0(cr= z-?t86@5i2SCcyrUKYQ#Up9R=cvUkK!h_V(~1=It)@Ax+ZDDv~D7e|0r|k)$&!XUUu&W%{l`A!*ILdv7NL2$`pF z{p*Gi%!MYu#|>R{?~h7de#G-SVCklxccA!v0zp+fTR5gce5}XLtl{dJ@Q)2PDz=|9 z7`VwL@n4j$X%AM#_68%#Wf8Y-YBqmA>;`7YS?QV}YdHOv2-aGHS z^Umzd?8ave&ot~nQ-8C-J4-ropR$bsj~yOpCiAzX)~Ou=FML4L#!c6>yZGBY83t{O zwgub;I9@iHwVk|3`Z-})NwGH&wr@JNpUikhzwX$2(J6SkpZ0{X*;Y$5N-F(hU+h1Q z`|&&$7zdzh?LnYQVLZ4J6JVPNOaiKbslW_?W1J1l1s+vc0A3iwVrU|vuoO%z1C|3T z03ApI^$HE(RRG69ocVtIYqen3X|mR*wShMQ!M>k6Xs4b6`)=?aVD|G19NMWTzr@jcq9X&(Y#1#17U^=PM_2>VU&7!>Q&g*(->Q%{9`7I-dj z^OwWhXs4b8`%3V7pxWNQmv(B#=>qQnE_ItTXs2eJ9JmM^KU>P5n)x3B9|NA;dwq!h z)DOdc8hjaeO4+GtzXJXT`2Ey;SqEyyslp`J03FJon*IrJC-6e6eu43+r^CJ*ya%`( z9R7lKYUX(ed=hwC#i3@LLGTc;^w0V#`crdWu7Pg@Ju5fL`ctz$RS#*}JmBosg2{0@U}0i7xi zHREvM*8!W9otpNQ;4WZX!>8jJhk6F=IdGA7_3l#h-Q5pfhnHq^Ie%!GKiCC&|81;g z9BNrl@CU$w*9SkMow^3`&x3yh=G1&F>qE`@41;e0XMTKp7X7I?t}48wO+exC9cO8$ zW*s`g&jBZPpX;Wbnsyib3Xn>O2HL5a&r$Gk;KrB9S81na{)6D}fcL-IHb^`5Bd}iq zUk9|=zuuvpn)8cB+BD#=e4Si})Le&k;1=SE&(_eNx*GOQ@Qc9Yfi*H8YUbmD3xKt4 z*Aw)oW_^x;KL_5ud-W^YsTuz~cnCQ5*1j9GQ!~yr@NM9YlgqBqPF)9k9X7TG;KZ>v zKct;n<^w(gTzdJy1n$k$uHjx%Lm*spY(2ALqUvIN7Wv zbj!3wk7L^K(6qH(o|!kiVtWr#PZ<7E<&-kgy;;vG*cs^-`K3nsWj#}RgmJR!cgGdB zUhoBuhM;H(oF0OQQ|RX?KVMiP9q6uO6|*=@{G8#6xL>jCXY>i(@bw{ z88dAJj;Ci#-$+?PC9Wk>h>Wq;Luw`o};$7Pi3 zR;sV;9*0>xUSsssSdJH)4wmcQ`6!g@#rbtT(Puh^a8_dRyNs;Z)#3EpSelh(Xo~q^ zTuo6PP31hw<6`OM0+OSPwKL;+*tnLIRz^5J+5Zj~8x4E+M8+9epS9a_W_Qp&fH$); zz2=I#H%Iw3m-CCyYDx|x{`1~g2gK)dHJ-z9qCTsmr`6^9sP*aZDHtC1B~KV= z6mMs%`eX!d#(9BdEa$%8%mg_-?|9tg;{IwqqPy*dRNxuuK&zE26I(ErZkW%2ifh{U z#*N2Ap!Jzd)K0~Bt%9#rFN&}EfG^KI`);86QH8Oc*k%y#$1RT&UjwF{V;O#+W7lHQqWlFaZDGpk zsEQvC7>}1x@yc$7pNrz4L(|FNd}SbcNMRG}7M`%v!jIKC?d0=@>JM#v)5nKT5E}%> zHQF4gxeezlnpaN8%+f&BS?2d;EB$=6AI0~-ZX<76MOC-*dEKq*t{h#%7M5<!T0I`cXb0>+vRLF!uqjDe+~Xbx0##T~6C*?^LNgQDX5I YDa=1_+F1VKqe{MHoxX653|unselect shape (free drawing) unselect shape (free drawing) + + ["<Primary>m"] + select move tool + select move tool + KP_Add','plus']]]> increment the line width From 4907e357787adffbdbb852c525163aa217262737 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 15 Jun 2020 22:13:03 +0200 Subject: [PATCH 21/67] Introduce resize manipulation * `SCALE_PRESERVE` (preserve ratio) * `SCALE` (vertical, horizontal or both) There is a third transformation, `SCALE_ANGLE`, that is not used because it causes problems to the `elementFinder`. Probably a bug of Cairo.inStroke that do no consider scales. --- draw.js | 149 +++++++++++++----- extension.js | 1 + locale/draw-on-your-screen.pot | 6 + prefs.js | 2 + schemas/gschemas.compiled | Bin 3972 -> 4064 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 + 6 files changed, 126 insertions(+), 37 deletions(-) diff --git a/draw.js b/draw.js index 4b1be8c..5e88831 100644 --- a/draw.js +++ b/draw.js @@ -60,11 +60,11 @@ const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path(); const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; -const Manipulations = { MOVE: 100 }; +const Manipulations = { MOVE: 100, RESIZE: 101 }; var Tools = Object.assign({}, Shapes, Manipulations); const TextStates = { WRITTEN: 0, DRAWING: 1, WRITING: 2 }; -const Transformations = { TRANSLATION: 0, ROTATION: 1 }; -const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move" }; +const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, SCALE: 3, SCALE_ANGLE: 4 }; +const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; @@ -220,8 +220,8 @@ var DrawingArea = new Lang.Class({ this.elements[i].points[2] == this.elements[i].points[1] || this.elements[i].points[2] == this.elements[i].points[0]); - let showTextRectangle = this.elements[i].shape == Shapes.TEXT && this.transformingElement && this.transformingElement == this.elements[i]; - this.elements[i].buildCairo(cr, false, showTextRectangle); + this.elements[i].buildCairo(cr, { showTextRectangle: this.transformingElement && this.transformingElement == this.elements[i], + drawTextRectangle: this.finderPoint ? true : false }); if (this.finderPoint) this._findTransformingElement(cr, this.elements[i]); @@ -239,8 +239,8 @@ var DrawingArea = new Lang.Class({ } if (this.currentElement) { - let showTextRectangle = this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING; - this.currentElement.buildCairo(cr, this.textHasCursor, showTextRectangle); + this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, + showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING }) if (this.currentElement.fill && this.currentElement.line.lineWidth == 0) { // add a dummy stroke while drawing @@ -280,6 +280,7 @@ var DrawingArea = new Lang.Class({ let button = event.get_button(); let [x, y] = event.get_coords(); + let controlPressed = event.has_control_modifier(); let shiftPressed = event.has_shift_modifier(); if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { @@ -294,9 +295,9 @@ var DrawingArea = new Lang.Class({ } if (button == 1) { - if (this.currentTool == Manipulations.MOVE) { - if (this.elements.length) - this._startTransforming(x, y, shiftPressed); + if (Object.values(Manipulations).indexOf(this.currentTool) != -1) { + if (this.transformingElement) + this._startTransforming(x, y, controlPressed, shiftPressed); } else { this._startDrawing(x, y, shiftPressed); } @@ -439,10 +440,7 @@ var DrawingArea = new Lang.Class({ } }, - _startTransforming: function(stageX, stageY, duplicate) { - if (!this.transformingElement) - return; - + _startTransforming: function(stageX, stageY, controlPressed, duplicate) { let [success, startX, startY] = this.transform_stage_point(stageX, stageY); if (!success) @@ -460,7 +458,9 @@ var DrawingArea = new Lang.Class({ } if (this.currentTool == Manipulations.MOVE) - this.transformingElement.startTransformation(startX, startY, Transformations.TRANSLATION); + this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION); + else if (this.currentTool == Manipulations.RESIZE) + this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.SCALE : Transformations.SCALE_PRESERVE); this.finderPoint = null; @@ -486,6 +486,14 @@ var DrawingArea = new Lang.Class({ this.transformingElement.startTransformation(x, y, Transformations.TRANSLATION); } + if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { + this.transformingElement.stopTransformation(x, y); + this.transformingElement.startTransformation(x, y, Transformations.SCALE); + } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE) { + this.transformingElement.stopTransformation(x, y); + this.transformingElement.startTransformation(x, y, Transformations.SCALE_PRESERVE); + } + this.transformingElement.updateTransformation(x, y); this._redisplay(); }, @@ -636,7 +644,7 @@ var DrawingArea = new Lang.Class({ }, updatePointerCursor: function(controlPressed) { - if (this.currentTool == Manipulations.MOVE) + if (Object.values(Manipulations).indexOf(this.currentTool) != -1) this.setPointerCursor(this.transformingElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING)) this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); @@ -1096,7 +1104,7 @@ const DrawingElement = new Lang.Class({ }; }, - buildCairo: function(cr, showTextCursor, showTextRectangle) { + buildCairo: function(cr, params) { cr.setLineCap(this.line.lineCap); cr.setLineJoin(this.line.lineJoin); cr.setLineWidth(this.line.lineWidth); @@ -1120,8 +1128,19 @@ const DrawingElement = new Lang.Class({ if (transformation.type == Transformations.TRANSLATION) { cr.translate(transformation.slideX, transformation.slideY); } else if (transformation.type == Transformations.ROTATION) { - let centerWithSlide = this._getCenterWithSlide(transformation); - this._rotate(cr, transformation.angle, centerWithSlide[0], centerWithSlide[1]); + let center = this._getCenterWithSlideBefore(transformation); + this._rotate(cr, transformation.angle, center[0], center[1]); + } else if (transformation.type == Transformations.SCALE_PRESERVE) { + let center = this._getCenterWithSlideBefore(transformation); + this._scale(cr, transformation.scale, transformation.scale, center[0], center[1]); + } else if (transformation.type == Transformations.SCALE) { + let center = this._getCenterWithSlideBefore(transformation); + this._scale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); + } else if (transformation.type == Transformations.SCALE_ANGLE) { + let center = this._getCenterWithSlideBefore(transformation); + this._rotate(cr, transformation.angle, center[0], center[1]); + this._scale(cr, transformation.scale, 1, center[0], center[1]); + this._rotate(cr, -transformation.angle, center[0], center[1]); } }); @@ -1162,24 +1181,37 @@ const DrawingElement = new Lang.Class({ 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); + cr.showText(params.showTextCursor ? (this.text + "_") : this.text); - if (!showTextCursor) + if (!params.showTextCursor) this.textWidth = cr.getCurrentPoint()[0] - Math.min(points[0][0], points[1][0]) - cr.rectangle(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]), - this.textWidth, - Math.abs(points[1][1] - points[0][1])); - - if (!showTextRectangle) - cr.setLineWidth(0); + if (params.showTextRectangle || params.drawTextRectangle) { + cr.rectangle(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]), + this.textWidth, - Math.abs(points[1][1] - points[0][1])); + if (!params.showTextRectangle) + // Only draw the rectangle to find the element, not to show it. + cr.setLineWidth(0); + } } this.transformations.forEach(transformation => { if (transformation.type == Transformations.TRANSLATION) { cr.translate(-transformation.slideX, -transformation.slideY); } else if (transformation.type == Transformations.ROTATION) { - let centerWithSlide = this._getCenterWithSlide(transformation); - this._rotate(cr, -transformation.angle, centerWithSlide[0], centerWithSlide[1]); + let center = this._getCenterWithSlideBefore(transformation); + this._rotate(cr, -transformation.angle, center[0], center[1]); + } else if (transformation.type == Transformations.SCALE_PRESERVE) { + let center = this._getCenterWithSlideBefore(transformation); + this._scale(cr, 1 / transformation.scale, 1 / transformation.scale, center[0], center[1]); + } else if (transformation.type == Transformations.SCALE) { + let center = this._getCenterWithSlideBefore(transformation); + this._scale(cr, 1 / transformation.scaleX, 1 / transformation.scaleY, center[0], center[1]); + } else if (transformation.type == Transformations.SCALE_ANGLE) { + let center = this._getCenterWithSlideBefore(transformation); + this._rotate(cr, -transformation.angle, center[0], center[1]); + this._scale(cr, 1 / transformation.scale, 1, center[0], center[1]); + this._rotate(cr, transformation.angle, center[0], center[1]); } }); }, @@ -1226,16 +1258,32 @@ const DrawingElement = new Lang.Class({ attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; let transAttribute = ''; + // Do translations first. this.transformations.filter(transformation => transformation.type == Transformations.TRANSLATION) .forEach(transformation => { transAttribute += transAttribute ? ' ' : ' transform="'; transAttribute += `translate(${transformation.slideX},${transformation.slideY})`; }); - this.transformations.filter(transformation => transformation.type == Transformations.ROTATION) + this.transformations.filter(transformation => transformation.type != Transformations.TRANSLATION) + .reverse() .forEach(transformation => { transAttribute += transAttribute ? ' ' : ' transform="'; let center = this._getCenter(); - transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; + + if (transformation.type == Transformations.ROTATION) { + transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; + } else if (transformation.type == Transformations.SCALE_PRESERVE) { + transAttribute += `translate(${-center[0] * (transformation.scale - 1)},${-center[1] * (transformation.scale - 1)}) `; + transAttribute += `scale(${transformation.scale})`; + } else if (transformation.type == Transformations.SCALE) { + transAttribute += `translate(${-center[0] * (transformation.scaleX - 1)},${-center[1] * (transformation.scaleY - 1)}) `; + transAttribute += `scale(${transformation.scaleX},${transformation.scaleY})`; + } else if (transformation.type == Transformations.SCALE_ANGLE) { + transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]}) `; + transAttribute += `translate(${-center[0] * (transformation.scale - 1)},0) `; + transAttribute += `scale(${transformation.scale},1) `; + transAttribute += `rotate(${-transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; + } }); transAttribute += transAttribute ? '"' : ''; @@ -1376,23 +1424,43 @@ const DrawingElement = new Lang.Class({ this.transformations.push({ startX: startX, startY: startY, type: type, slideX: 0, slideY: 0 }); else if (type == Transformations.ROTATION) this.transformations.push({ startX: startX, startY: startY, type: type, angle: 0 }); + else if (type == Transformations.SCALE_PRESERVE) + this.transformations.push({ startX: startX, startY: startY, type: type, scale: 1 }); + else if (type == Transformations.SCALE) + this.transformations.push({ startX: startX, startY: startY, type: type, scaleX: 1, scaleY: 1 }); + else if (type == Transformations.SCALE_ANGLE) + this.transformations.push({ startX: startX, startY: startY, type: type, scale: 1, angle: 0 }); }, updateTransformation: function(x, y) { - let transformation = this.transformations[this.transformations.length - 1]; + let transformation = this.lastTransformation; if (transformation.type == Transformations.TRANSLATION) { transformation.slideX = x - transformation.startX; transformation.slideY = y - transformation.startY; } else if (transformation.type == Transformations.ROTATION) { - let centerWithSlide = this._getCenterWithSlide(transformation); - transformation.angle = getAngle(centerWithSlide[0], centerWithSlide[1], transformation.startX, transformation.startY, x, y); + let center = this._getCenterWithSlideBefore(transformation); + transformation.angle = getAngle(center[0], center[1], transformation.startX, transformation.startY, x, y); + } else if (transformation.type == Transformations.SCALE_PRESERVE) { + let center = this._getCenterWithSlideBefore(transformation); + transformation.scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]); + } else if (transformation.type == Transformations.SCALE) { + let center = this._getCenterWithSlideBefore(transformation); + let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY); + let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(3 * Math.PI / 8); + let horizontal = Math.abs(Math.cos(startAngle)) >= Math.cos(Math.PI / 8); + transformation.scaleX = vertical ? 1 : Math.abs((x - center[0]) / (transformation.startX - center[0])); + transformation.scaleY = horizontal ? 1 : Math.abs((y - center[1]) / (transformation.startY - center[1])); + } else if (transformation.type == Transformations.SCALE_ANGLE) { + let center = this._getCenterWithSlideBefore(transformation); + transformation.scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]); + transformation.angle = getAngle(center[0], center[1], center[0] + 1, center[1], x, y); } }, stopTransformation: function(x, y) { // Clean transformations - let transformation = this.transformations[this.transformations.length - 1]; + let transformation = this.lastTransformation; if (transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < 1 || transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < Math.PI / 1000) { @@ -1430,11 +1498,18 @@ const DrawingElement = new Lang.Class({ }, // The figure rotation center that takes previous translations into account. - _getCenterWithSlide: function(transformation) { + _getCenterWithSlideBefore: function(transformation) { let [center, totalSlide] = [this._getCenter(), this._getTotalSlideBefore(transformation)]; return [center[0] + totalSlide[0], center[1] + totalSlide[1]]; }, + _getCenterWithSlide: function() { + if (this.transformations.length) + return this._getCenterWithSlideBefore(this.lastTransformation); + else + return this._getCenter(); + }, + _addPoint: function(x, y, smoothedStroke) { this.points.push([x, y]); if (smoothedStroke) @@ -1450,7 +1525,7 @@ const DrawingElement = new Lang.Class({ }, _scale: function(cr, scaleX, scaleY, x, y) { - if (scaleX == scaleY) + if (scaleX == 1 && scaleY == 1) return; cr.translate(x, y); cr.scale(scaleX, scaleY); @@ -1937,7 +2012,7 @@ const DrawingMenu = new Lang.Class({ else text = _(obj[i]); - let iCaptured = i; + let iCaptured = Number(i); let subItem = item.menu.addAction(text, () => { item.label.set_text(_(obj[iCaptured])); target[targetProperty] = iCaptured; diff --git a/extension.js b/extension.js index 98a7f23..606636c 100644 --- a/extension.js +++ b/extension.js @@ -195,6 +195,7 @@ var AreaManager = new Lang.Class({ 'select-polygon-shape': () => this.activeArea.selectTool(Draw.Tools.POLYGON), 'select-polyline-shape': () => this.activeArea.selectTool(Draw.Tools.POLYLINE), 'select-move-tool': () => this.activeArea.selectTool(Draw.Tools.MOVE), + 'select-resize-tool': () => this.activeArea.selectTool(Draw.Tools.RESIZE), 'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea), 'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea), 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index c6c205d..c0ad135 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -68,6 +68,9 @@ msgstr "" msgid "Move" msgstr "" +msgid "Resize" +msgstr "" + msgid "Fill" msgstr "" @@ -185,6 +188,9 @@ msgstr "" msgid "Select move" msgstr "" +msgid "Select resize" +msgstr "" + msgid "Toggle fill/stroke" msgstr "" diff --git a/prefs.js b/prefs.js index 7150319..386a7dc 100644 --- a/prefs.js +++ b/prefs.js @@ -51,8 +51,10 @@ var INTERNAL_KEYBINDINGS = { 'select-ellipse-shape': "Select ellipse", 'select-rectangle-shape': "Select rectangle", 'select-polygon-shape': "Select polygon", + 'select-polyline-shape': "Select polyline", 'select-text-shape': "Select text", 'select-move-tool': "Select move", + 'select-resize-tool': "Select resize", 'toggle-fill': "Toggle fill/stroke", '-separator-2': '', 'increment-line-width': "Increment line width", diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 6c557a298c87f442230762c3e457776cff4d62ff..7a60b580426718781d8919afad21711f8ffad2a7 100644 GIT binary patch literal 4064 zcmaJ^Yitx%7@bPd%B#zx(3YwNLAJLT8iT@g_jY&M*_m}_wlA>3 zS2Umr8WS6o2+@cb3=owx#Dx0Cpos}2Mr-s37)|hpq(3xZ4B|PncW0(cb&`|w&6#h$ z``yRf`_1MjG+Wn9SCLmaxFcsr?~qy%xcKs51DVfaN~`h;c-NhZGPO)mZsGc&6oRr% z`3Za(u zQ-ERs%_(;Q(|{SkOdtZx1CWM?`CwuJz`j%h3xO)&9-ume#juGb5=+4k6kr+dYb2I~ z!-(O&5UZdOt3#;yt+wQT+;hBUE4@kv{2Xxd%V{rAr(FvD5cn9-5k3DVb=vcwkAS}d z9_j!6B6Zp`p^t%Y0FO$YHuVw=QWUuMY3zCW)27}8-UhTtf7;adfcFEX*Y_@=KkYft zhruU+$D}`P`j3FefaB+GM(9tQ{^Q_3fzd8~K6TpEOG*@_0@x(u&}N({xE0teb=uTB z!2Lka-}P0DLz{69fsX-0FP?sfI_(PRqu`%`RvCvj)$d!7V`5^(vb>?HSP9z#7oKa&rT9+H5xiei>+d_@i~yX-A+BgFoi8 zW6Q^=)2@bo5qt%xd~&hZFWT(aIJjuKqFitfenNlR9EWmn9ncvS-uTd_-U{9h{CIqk z7l$_Ebbxi>$f;a>+SL2O2Y|z`?!CtNv>E3p_zW=q-N7ByX|rFW;7h>J)~a8q)8_bG z1uw@3pWka}bFWzez5ztcef{*O&2cNi#xN5&wf)FG>a>fYmxEUVqt6aa=blA-D)c6B z50HIm=PCNr=6vl3zYZ*{c-Ma@$CH-WbRM-Lx(n>uaI?;h}n)c2h2qE4H6j)E@%Z+*Vw zGa>~91u)M8*GeW0p>|oOt2*w0A>gyV&DPVJJ)Nb4M5=?y4Ef7)avKa8g=kLY#q#esRmN?7C1pkGJiv3tFvHW>t92%CMe0 z_iMjh_^%4?R^|PLtvN#F(WRS7`foJc=JoB_jIf&pRxi5}j#&>MUg5a)!Ciemu6)N9 zjy@<<*RqU)_xdO49}bsr+||L|>U>{{?mUy#Y@q^LLBCCzM?#=NNDC|>E)lg) zPq-;HZP{D|xct1~%XmNO%1>>C1oRps@UeK)szQfSs50=%0b(NT8U9I(Ehx(1Y)&_a4&F3Y@ zw^NJvByB5e=BMA!qf?HrZr(n=1^wQbNCYj&an2Z7M~N)Wjj+~>DRk6s#|pk*N5;1@ z!c9Yk3z$h(M(~enwd}&R=h{; z)g8T4H*|L(oOepLF9#xN>)sIf{ZEGTL(9FomCbd*8)4ti%d^GmGqI&I2RT>XI7|l-bdM`rFDbd3C5voVjO-9aV--(?~%_Z z`(DCv!XJpAL|Qj7o&K$y|+(#ET|E5NL^u?2f| z^=$A`fc1C{Fa;-KhpzfKI{3f0gYSH%&_9hPxhV7mgt%+AVSJbS&(*N+xzKP%(O z?~H3-wX@z?KOs-7yX{%S)iYSve&^6QwnL{UP-J)f2W87Pgi|psoB^RUf)_LSaY|bj zPKd&O`ArI(IELI$WdG6@_Ahz#`hIB{&)b=@j=--fOBhVH;6V9)mh_u!AN~Jxireaf faOZ3kI-1FnDd?f!j-Bsct{(c&`7op(2OrnJ;8?~t literal 3972 zcmZ`+Z)jC@7(SNE<=<(}+?+K{^G-c?n{%5RHn*C(EMa395|KN*_jm6-+qvgl&pCJ7 zohCzt?2ix;B$l)Yl0wkPL`2ZO#0&yIL@9k)P+%Vt14AXup6A@(J?Gpt2M^DCp7Wmf zeSd%NyX~)Nwyv43BL9|xcjxWMBT}0Io;f+%Nank!(xn^$w?C#RCFP3p0Dof(ASf>@ zfUEEriK$mB>bq6oq-1`%$0- zm<2oz%mwBFOclmLJQuJC_F@2K`FIjG$5ja|1FC?hLRbNtct&Csm{>gpYw#QaYNlWv zo{9B96sVKf0PY<5^XhUi=W3oZq;!Kbz*ec#ralH92ewI_HuW>$tH5@t)22QN=ER)5 zaKDW4X)l7l6uca`oY0q0r%k;M+yQjSIJCWbz!~7hLOoI+1CIkG^`FmX9NJ3|=REid zutWOOW}Iu_yFk;HFI(wPy9|11DQW7h=0KJ**ld%&BYuDwQ` zb~W^BY_NKu%-laho%USl?clAzoomovz}w%FMtX6ov*3W z=6ElIuLHY}o%@VBZTjB=&zP+!Cr+=lsMDSUy$rk(sC+fA)23br?f?!PKJzjCX;(n+ z10MtyjJDQOr_H>_z$bv$n$-)`X_rGk555A_{83v)YUqa}>qs z*|UZIw3+uB_-~+W(*Bn^?I)m@;;@zjx6{?$xM;KfYH$lI&Ic-1bh`3sN3$1mp1F41m6Lkm*_DKW+N= zfyV$x7$WAXG0U**27I@<+t$;Xoojokdrw2~)S%3-P$HUB6ZYqQANJoEJZ+rLzj2Cx zW8mL7oqy95|E9pdso<}haXqHFmaWEhN9#9493Odqk^UOjN+u1VCM?rchlQR@xj}?s zEZ5WlH5ZSkIq+^SBx@#%aZ*}Fgub(g$CZXvH+up!;bI_iL9nZ@(ittG{tPHWi2^%a_Ujz5SR8jn#t z4`|Xd4Cv4IwtC!Op-qsqbuNkjJ}JMC3v_U4ffM4T8rI`(N=;if_9*{+f4MKP9ecC= zuC2vf^kIyj5w@c{=nh?#6QrBT{Fj(lIWt`AhmIp#JPz-yI&RLGnqQZG2+qE)0(cr= z-?t86@5i2SCcyrUKYQ#Up9R=cvUkK!h_V(~1=It)@Ax+ZDDv~D7e|0r|k)$&!XUUu&W%{l`A!*ILdv7NL2$`pF z{p*Gi%!MYu#|>R{?~h7de#G-SVCklxccA!v0zp+fTR5gce5}XLtl{dJ@Q)2PDz=|9 z7`VwL@n4j$X%AM#select move tool select move tool + + ["<Primary>x"] + select resize tool + select resize tool + KP_Add','plus']]]> increment the line width From c26c8cd2b50792a15724e50ba82d90d8a37af777 Mon Sep 17 00:00:00 2001 From: abakkk Date: Tue, 16 Jun 2020 00:06:52 +0200 Subject: [PATCH 22/67] misc dash fixes * Use `setDash([0,0], 0)` to disable dash. * Disable dashes when drawing the grid. * Add `crSetDummyStroke` utility function. --- draw.js | 93 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/draw.js b/draw.js index 5e88831..60418da 100644 --- a/draw.js +++ b/draw.js @@ -242,17 +242,15 @@ var DrawingArea = new Lang.Class({ this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING }) - if (this.currentElement.fill && this.currentElement.line.lineWidth == 0) { - // add a dummy stroke while drawing - cr.setLineWidth(2); - cr.setDash([1, 2], 0); - } + if (this.currentElement.fill && this.currentElement.line.lineWidth == 0) + crSetDummyStroke(cr); cr.stroke(); } if (this.isInDrawingMode && this.hasGrid && this.gridGap && this.gridGap >= 1) { Clutter.cairo_set_source_color(cr, this.gridColor); + cr.setDash([], 0); let [gridX, gridY] = [this.gridGap, this.gridGap]; while (gridX < this.monitor.width) { @@ -538,8 +536,6 @@ var DrawingArea = new Lang.Class({ }); if (this.currentTool == Shapes.TEXT) { - this.currentElement.line = { lineWidth: 2, lineJoin: 0, lineCap: 0 }; - this.currentElement.dash = { active: true, array: [1, 2] , offset: 0 }; this.currentElement.fill = false; this.currentElement.text = _("Text"); this.currentElement.textState = TextStates.DRAWING; @@ -1113,7 +1109,7 @@ const DrawingElement = new Lang.Class({ if (this.dash.active) cr.setDash(this.dash.array, this.dash.offset); else - cr.setDash([1000000], 0); + cr.setDash([], 0); if (this.eraser) cr.setOperator(Cairo.Operator.CLEAR); @@ -1129,18 +1125,18 @@ const DrawingElement = new Lang.Class({ cr.translate(transformation.slideX, transformation.slideY); } else if (transformation.type == Transformations.ROTATION) { let center = this._getCenterWithSlideBefore(transformation); - this._rotate(cr, transformation.angle, center[0], center[1]); + crRotate(cr, transformation.angle, center[0], center[1]); } else if (transformation.type == Transformations.SCALE_PRESERVE) { let center = this._getCenterWithSlideBefore(transformation); - this._scale(cr, transformation.scale, transformation.scale, center[0], center[1]); + crScale(cr, transformation.scale, transformation.scale, center[0], center[1]); } else if (transformation.type == Transformations.SCALE) { let center = this._getCenterWithSlideBefore(transformation); - this._scale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); + crScale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); } else if (transformation.type == Transformations.SCALE_ANGLE) { let center = this._getCenterWithSlideBefore(transformation); - this._rotate(cr, transformation.angle, center[0], center[1]); - this._scale(cr, transformation.scale, 1, center[0], center[1]); - this._rotate(cr, -transformation.angle, center[0], center[1]); + crRotate(cr, transformation.angle, center[0], center[1]); + crScale(cr, transformation.scale, 1, center[0], center[1]); + crRotate(cr, -transformation.angle, center[0], center[1]); } }); @@ -1161,10 +1157,10 @@ const DrawingElement = new Lang.Class({ if (points[2]) ratio = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]) / Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]); - this._scale(cr, ratio, 1, points[0][0], points[0][1]); + crScale(cr, ratio, 1, points[0][0], points[0][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 / ratio, 1, points[0][0], points[0][1]); + crScale(cr, 1 / ratio, 1, points[0][0], points[0][1]); } else if (shape == Shapes.RECTANGLE && points.length == 2) { cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]); @@ -1189,7 +1185,9 @@ const DrawingElement = new Lang.Class({ if (params.showTextRectangle || params.drawTextRectangle) { cr.rectangle(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]), this.textWidth, - Math.abs(points[1][1] - points[0][1])); - if (!params.showTextRectangle) + if (params.showTextRectangle) + crSetDummyStroke(cr); + else // Only draw the rectangle to find the element, not to show it. cr.setLineWidth(0); } @@ -1200,18 +1198,18 @@ const DrawingElement = new Lang.Class({ cr.translate(-transformation.slideX, -transformation.slideY); } else if (transformation.type == Transformations.ROTATION) { let center = this._getCenterWithSlideBefore(transformation); - this._rotate(cr, -transformation.angle, center[0], center[1]); + crRotate(cr, -transformation.angle, center[0], center[1]); } else if (transformation.type == Transformations.SCALE_PRESERVE) { let center = this._getCenterWithSlideBefore(transformation); - this._scale(cr, 1 / transformation.scale, 1 / transformation.scale, center[0], center[1]); + crScale(cr, 1 / transformation.scale, 1 / transformation.scale, center[0], center[1]); } else if (transformation.type == Transformations.SCALE) { let center = this._getCenterWithSlideBefore(transformation); - this._scale(cr, 1 / transformation.scaleX, 1 / transformation.scaleY, center[0], center[1]); + crScale(cr, 1 / transformation.scaleX, 1 / transformation.scaleY, center[0], center[1]); } else if (transformation.type == Transformations.SCALE_ANGLE) { let center = this._getCenterWithSlideBefore(transformation); - this._rotate(cr, -transformation.angle, center[0], center[1]); - this._scale(cr, 1 / transformation.scale, 1, center[0], center[1]); - this._rotate(cr, transformation.angle, center[0], center[1]); + crRotate(cr, -transformation.angle, center[0], center[1]); + crScale(cr, 1 / transformation.scale, 1, center[0], center[1]); + crRotate(cr, transformation.angle, center[0], center[1]); } }); }, @@ -1230,7 +1228,7 @@ const DrawingElement = new Lang.Class({ if (this.dash.active) cr.setDash(this.dash.array, this.dash.offset); else - cr.setDash([1000000], 0); + cr.setDash([], 0); return inElement; }, @@ -1514,25 +1512,40 @@ const DrawingElement = new Lang.Class({ this.points.push([x, y]); if (smoothedStroke) this.smooth(this.points.length - 1); - }, - - _rotate: function(cr, angle, x, y) { - if (angle == 0) - return; - cr.translate(x, y); - cr.rotate(angle); - cr.translate(-x, -y); - }, - - _scale: function(cr, scaleX, scaleY, x, y) { - if (scaleX == 1 && scaleY == 1) - return; - cr.translate(x, y); - cr.scale(scaleX, scaleY); - cr.translate(-x, -y); } }); +/* + Some Cairo utils +*/ + +const crSetDummyStroke = function(cr) { + cr.setLineWidth(2); + cr.setLineCap(0); + cr.setLineJoin(0); + cr.setDash([1, 2], 0); +}; + +const crRotate = function(cr, angle, x, y) { + if (angle == 0) + return; + cr.translate(x, y); + cr.rotate(angle); + cr.translate(-x, -y); +}; + +const crScale = function(cr, scaleX, scaleY, x, y) { + if (scaleX == 1 && scaleY == 1) + return; + cr.translate(x, y); + cr.scale(scaleX, scaleY); + cr.translate(-x, -y); +}; + +/* + Some geometric utils +*/ + const getNearness = function(pointA, pointB, distance) { return Math.hypot(pointB[0] - pointA[0], pointB[1] - pointA[1]) < distance; }; From 6a0c0e525ab17a3c173b9b0a4be433308436e2ff Mon Sep 17 00:00:00 2001 From: abakkk Date: Wed, 17 Jun 2020 15:27:18 +0200 Subject: [PATCH 23/67] cr.save, cr.stroke, cr.identityMatrix * Use `cr.save` and `cr.stroke` (Restore the context but not the path). * Use `cr.identityMatrix` to clean context after transformations. Inverting transformations was not enough, in particular with combinations of scale and rotate (`SCALE_ANGLE`, `REFLECTION`, ...). Now all elements can be grabbed correctly. --- draw.js | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/draw.js b/draw.js index 60418da..18385f4 100644 --- a/draw.js +++ b/draw.js @@ -215,6 +215,7 @@ var DrawingArea = new Lang.Class({ let cr = this.get_context(); for (let i = 0; i < this.elements.length; i++) { + cr.save(); let isStraightLine = this.elements[i].shape == Shapes.LINE && (this.elements[i].points.length < 3 || this.elements[i].points[2] == this.elements[i].points[1] || @@ -236,9 +237,11 @@ var DrawingArea = new Lang.Class({ } else { cr.stroke(); } + cr.restore(); } if (this.currentElement) { + cr.save(); this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING }) @@ -246,9 +249,11 @@ var DrawingArea = new Lang.Class({ crSetDummyStroke(cr); cr.stroke(); + cr.restore(); } if (this.isInDrawingMode && this.hasGrid && this.gridGap && this.gridGap >= 1) { + cr.save(); Clutter.cairo_set_source_color(cr, this.gridColor); cr.setDash([], 0); @@ -267,6 +272,7 @@ var DrawingArea = new Lang.Class({ gridY += this.gridGap; cr.stroke(); } + cr.restore(); } cr.$dispose(); @@ -405,10 +411,9 @@ var DrawingArea = new Lang.Class({ else if (this.transformingElement == element) this.transformingElement = null; - if (element == this.elements[this.elements.length - 1]) { + if (element == this.elements[this.elements.length - 1]) // All elements have been tested, the winner is the last. this.updatePointerCursor(); - } }, _startElementFinder: function() { @@ -1193,42 +1198,20 @@ const DrawingElement = new Lang.Class({ } } - this.transformations.forEach(transformation => { - if (transformation.type == Transformations.TRANSLATION) { - cr.translate(-transformation.slideX, -transformation.slideY); - } else if (transformation.type == Transformations.ROTATION) { - let center = this._getCenterWithSlideBefore(transformation); - crRotate(cr, -transformation.angle, center[0], center[1]); - } else if (transformation.type == Transformations.SCALE_PRESERVE) { - let center = this._getCenterWithSlideBefore(transformation); - crScale(cr, 1 / transformation.scale, 1 / transformation.scale, center[0], center[1]); - } else if (transformation.type == Transformations.SCALE) { - let center = this._getCenterWithSlideBefore(transformation); - crScale(cr, 1 / transformation.scaleX, 1 / transformation.scaleY, center[0], center[1]); - } else if (transformation.type == Transformations.SCALE_ANGLE) { - let center = this._getCenterWithSlideBefore(transformation); - crRotate(cr, -transformation.angle, center[0], center[1]); - crScale(cr, 1 / transformation.scale, 1, center[0], center[1]); - crRotate(cr, transformation.angle, center[0], center[1]); - } - }); + cr.identityMatrix(); }, getContainsPoint: function(cr, x, y) { if (this.shape == Shapes.TEXT) return cr.inFill(x, y); + cr.save(); cr.setLineWidth(Math.max(this.line.lineWidth, 25)); cr.setDash([], 0); // Check whether the point is inside/on/near the element. - let inElement = cr.inStroke(x, y) || this.fill && cr.inFill(x, y); - - cr.setLineWidth(this.line.lineWidth); - if (this.dash.active) - cr.setDash(this.dash.array, this.dash.offset); - else - cr.setDash([], 0); + let inElement = cr.inStroke(x, y) || this.fill && cr.inFill(x, y); + cr.restore(); return inElement; }, From 317f9e41764ec11332cd9bcc27b51598e28ff65c Mon Sep 17 00:00:00 2001 From: abakkk Date: Wed, 17 Jun 2020 15:37:37 +0200 Subject: [PATCH 24/67] invert fill and stroke painting Use `cr.fillPreserve, ..., then cr.stroke` instead of `cr.copyPath, ..., cr.stroke, cr.appendPath, cr.fill` Was there a reason for painting stroke before fill ? Is something broken ? --- draw.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/draw.js b/draw.js index 18385f4..4768b87 100644 --- a/draw.js +++ b/draw.js @@ -228,15 +228,12 @@ var DrawingArea = new Lang.Class({ this._findTransformingElement(cr, this.elements[i]); if (this.elements[i].fill && !isStraightLine) { - let pathCopy = cr.copyPath(); + cr.fillPreserve(); if (this.elements[i].shape == Shapes.NONE || this.elements[i].shape == Shapes.LINE) cr.closePath(); - cr.stroke(); // first paint stroke - cr.appendPath(pathCopy); - cr.fill(); // secondly paint fill - } else { - cr.stroke(); - } + } + + cr.stroke(); cr.restore(); } From 2658926c442c9fd1b56da87a7649c6787f56e4dd Mon Sep 17 00:00:00 2001 From: abakkk Date: Wed, 17 Jun 2020 15:43:23 +0200 Subject: [PATCH 25/67] Add debug constants and helpers --- draw.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/draw.js b/draw.js index 4768b87..26a12b2 100644 --- a/draw.js +++ b/draw.js @@ -48,6 +48,9 @@ const Prefs = Me.imports.prefs; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const GS_VERSION = Config.PACKAGE_VERSION; +const CAIRO_DEBUG_EXTENDS = false; +const SVG_DEBUG_EXTENDS = false; +const SVG_DEBUG_SUPERPOSES_CAIRO = false; const ICON_DIR = Me.dir.get_child('data').get_child('icons'); const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path(); @@ -213,6 +216,10 @@ var DrawingArea = new Lang.Class({ vfunc_repaint: function() { let cr = this.get_context(); + if (CAIRO_DEBUG_EXTENDS) { + cr.scale(0.5, 0.5); + cr.translate(this.monitor.width, this.monitor.height); + } for (let i = 0; i < this.elements.length; i++) { cr.save(); @@ -897,10 +904,16 @@ var DrawingArea = new Lang.Class({ } let content = ``; + if (SVG_DEBUG_EXTENDS) + content = ``; let backgroundColorString = this.hasBackground ? this.activeBackgroundColor.to_string() : 'transparent'; if (backgroundColorString != 'transparent') { content += `\n `; } + if (SVG_DEBUG_EXTENDS) { + content += `\n `; + content += `\n `; + } for (let i = 0; i < this.elements.length; i++) { content += this.elements[i].buildSVG(backgroundColorString); } @@ -1121,6 +1134,10 @@ const DrawingElement = new Lang.Class({ let [success, color] = Clutter.Color.from_string(this.color); if (success) Clutter.cairo_set_source_color(cr, color); + if (SVG_DEBUG_SUPERPOSES_CAIRO) { + Clutter.cairo_set_source_color(cr, Clutter.Color.from_string("red")[1]); + cr.setLineWidth(this.line.lineWidth / 2 || 1); + } this.transformations.slice(0).reverse().forEach(transformation => { if (transformation.type == Transformations.TRANSLATION) { From 15d3efcbd54a4eae1d92d6e7762562db2a57eebc Mon Sep 17 00:00:00 2001 From: abakkk Date: Wed, 17 Jun 2020 16:07:36 +0200 Subject: [PATCH 26/67] user.css * Add a warning about updates. * Add alternative color palettes. * Change background color (pure dark gray). --- data/default.css | 92 +++++++++++++++++++++++++++++++++++++++++++++--- draw.js | 3 ++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/data/default.css b/data/default.css index 6d952f6..466cd15 100644 --- a/data/default.css +++ b/data/default.css @@ -1,9 +1,11 @@ /* - * Except for the font, you don't need to restart the extension. - * Just save this file as ~/.local/share/drawOnYourScreen/user.css and the changes will be applied for your next brushstroke. + * WARNING : user.css may be obsolete after an extension update. * * ~/.local/share/drawOnYourScreen/user.css file is automatically generated by activating "Edit style". - * Delete ~/.local/share/drawOnYourScreen/user.css file to retrieve default drawing style. + * Delete ~/.local/share/drawOnYourScreen/user.css file to retrieve the default drawing style. + * + * Except for the font, you don't need to restart the extension. + * Just save this file as ~/.local/share/drawOnYourScreen/user.css and the changes will be applied for your next brushstroke. * * line-join (no string): * 0 : miter, 1 : round, 2 : bevel @@ -40,7 +42,7 @@ /*-drawing-dash-array-on: 5px;*/ /*-drawing-dash-array-off: 15px;*/ /*-drawing-dash-offset: 0px;*/ - -drawing-background-color: #2e3436; + -drawing-background-color: #2e2e2e; -grid-overlay-gap: 10px; -grid-overlay-line-width: 0.4px; -grid-overlay-interline-width: 0.2px; @@ -64,3 +66,85 @@ -drawing-color8: Gray; -drawing-color9: Black; } + +/* +Example of alternative palettes from GNOME HIG Colors. +https://developer.gnome.org/hig/stable/icon-design.html + +The last uncommented palette wins. +*/ + +/* lighter */ +/* +.draw-on-your-screen { + -drawing-color1: rgb(153, 193, 241); + -drawing-color2: rgb(143, 240, 164); + -drawing-color3: rgb(249, 240, 107); + -drawing-color4: rgb(255, 190, 111); + -drawing-color5: rgb(246, 97, 81); + -drawing-color6: rgb(220, 138, 221); + -drawing-color7: rgb(205, 171, 143); + -drawing-color8: rgb(255, 255, 255); + -drawing-color9: rgb(119, 118, 123); +} +*/ + +/* light */ +/* +.draw-on-your-screen { + -drawing-color1: rgb( 98, 160, 241); + -drawing-color2: rgb( 87, 227, 137); + -drawing-color3: rgb(248, 228, 92); + -drawing-color4: rgb(255, 163, 72); + -drawing-color5: rgb(237, 51, 59); + -drawing-color6: rgb(192, 97, 203); + -drawing-color7: rgb(181, 131, 90); + -drawing-color8: rgb(246, 245, 244); + -drawing-color9: rgb( 94, 92, 100); +} +*/ + +/* normal */ +/* +.draw-on-your-screen { + -drawing-color1: rgb( 53, 132, 228); + -drawing-color2: rgb( 51, 209, 122); + -drawing-color3: rgb(246, 211, 45); + -drawing-color4: rgb(255, 120, 0); + -drawing-color5: rgb(224, 27, 36); + -drawing-color6: rgb(145, 65, 172); + -drawing-color7: rgb(152, 106, 68); + -drawing-color8: rgb(222, 221, 218); + -drawing-color9: rgb( 61, 56, 70); +} +*/ + +/* dark */ +/* +.draw-on-your-screen { + -drawing-color1: rgb( 28, 113, 216); + -drawing-color2: rgb( 46, 194, 126); + -drawing-color3: rgb(245, 194, 17); + -drawing-color4: rgb(230, 97, 0); + -drawing-color5: rgb(192, 28, 40); + -drawing-color6: rgb(129, 61, 156); + -drawing-color7: rgb(134, 94, 60); + -drawing-color8: rgb(192, 191, 188); + -drawing-color9: rgb( 36, 31, 49); +} +*/ + +/* darker */ +/* +.draw-on-your-screen { + -drawing-color1: rgb( 26, 095, 180); + -drawing-color2: rgb( 38, 162, 105); + -drawing-color3: rgb(229, 165, 10); + -drawing-color4: rgb(198, 70, 0); + -drawing-color5: rgb(165, 29, 45); + -drawing-color6: rgb( 97, 53, 131); + -drawing-color7: rgb( 99, 69, 44); + -drawing-color8: rgb(154, 153, 150); + -drawing-color9: rgb( 0, 0, 0); +} +*/ diff --git a/draw.js b/draw.js index 26a12b2..ea8cb87 100644 --- a/draw.js +++ b/draw.js @@ -837,6 +837,9 @@ var DrawingArea = new Lang.Class({ this._onKeyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this)); this.scrollHandler = this.connect('scroll-event', this._onScroll.bind(this)); this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null); + if (this.hasGrid) + // redisplay to show the grid before updating the style because _updateStyle is long. + this._redisplay(); this._updateStyle(); }, From fa4223b7ce185241670f45965e7cc9757dac4ddf Mon Sep 17 00:00:00 2001 From: abakkk Date: Wed, 17 Jun 2020 18:30:57 +0200 Subject: [PATCH 27/67] Introduce mirror manipulations * Reflection (symmetry axis) * Inversion (symmetry point) `transformingElement` is locked after selecting it. --- draw.js | 132 +++++++++++++++--- extension.js | 1 + locale/draw-on-your-screen.pot | 6 + prefs.js | 1 + schemas/gschemas.compiled | Bin 4064 -> 4140 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 + 6 files changed, 128 insertions(+), 17 deletions(-) diff --git a/draw.js b/draw.js index ea8cb87..d2a115d 100644 --- a/draw.js +++ b/draw.js @@ -63,11 +63,11 @@ const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path(); const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; -const Manipulations = { MOVE: 100, RESIZE: 101 }; +const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; var Tools = Object.assign({}, Shapes, Manipulations); const TextStates = { WRITTEN: 0, DRAWING: 1, WRITING: 2 }; -const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, SCALE: 3, SCALE_ANGLE: 4 }; -const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize" }; +const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, SCALE: 3, SCALE_ANGLE: 4, REFLECTION: 5, INVERSION: 6 }; +const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; @@ -422,7 +422,7 @@ var DrawingArea = new Lang.Class({ _startElementFinder: function() { this.elementFinderHandler = this.connect('motion-event', (actor, event) => { - if (this.motionHandler) { + if (this.motionHandler || this.transformingElementLocked) { this.finderPoint = null; return; } @@ -453,6 +453,16 @@ var DrawingArea = new Lang.Class({ if (!success) return; + if (this.currentTool == Manipulations.MIRROR) { + this.transformingElementLocked = !this.transformingElementLocked; + if (this.transformingElementLocked) { + this.updatePointerCursor(); + return; + } + } + + this.finderPoint = null; + this.buttonReleasedHandler = this.connect('button-release-event', (actor, event) => { this._stopTransforming(); }); @@ -468,8 +478,11 @@ var DrawingArea = new Lang.Class({ this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION); else if (this.currentTool == Manipulations.RESIZE) this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.SCALE : Transformations.SCALE_PRESERVE); + else if (this.currentTool == Manipulations.MIRROR) { + this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION); + this._redisplay(); + } - this.finderPoint = null; this.motionHandler = this.connect('motion-event', (actor, event) => { if (this.spaceKeyPressed) @@ -486,21 +499,29 @@ var DrawingArea = new Lang.Class({ _updateTransforming: function(x, y, controlPressed) { if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.TRANSLATION) { - this.transformingElement.stopTransformation(x, y); + this.transformingElement.stopTransformation(); this.transformingElement.startTransformation(x, y, Transformations.ROTATION); } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.ROTATION) { - this.transformingElement.stopTransformation(x, y); + this.transformingElement.stopTransformation(); this.transformingElement.startTransformation(x, y, Transformations.TRANSLATION); } if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { - this.transformingElement.stopTransformation(x, y); + this.transformingElement.stopTransformation(); this.transformingElement.startTransformation(x, y, Transformations.SCALE); } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE) { - this.transformingElement.stopTransformation(x, y); + this.transformingElement.stopTransformation(); this.transformingElement.startTransformation(x, y, Transformations.SCALE_PRESERVE); } + if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.REFLECTION) { + this.transformingElement.transformations.pop(); + this.transformingElement.startTransformation(x, y, Transformations.INVERSION); + } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.INVERSION) { + this.transformingElement.transformations.pop(); + this.transformingElement.startTransformation(x, y, Transformations.REFLECTION); + } + this.transformingElement.updateTransformation(x, y); this._redisplay(); }, @@ -517,6 +538,7 @@ var DrawingArea = new Lang.Class({ this.transformingElement.stopTransformation(); this.transformingElement = null; + this.transformingElementLocked = false; this._redisplay(); }, @@ -649,7 +671,9 @@ var DrawingArea = new Lang.Class({ }, updatePointerCursor: function(controlPressed) { - if (Object.values(Manipulations).indexOf(this.currentTool) != -1) + if (this.currentTool == Manipulations.MIRROR && this.transformingElementLocked) + this.setPointerCursor('CROSSHAIR'); + else if (Object.values(Manipulations).indexOf(this.currentTool) != -1) this.setPointerCursor(this.transformingElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING)) this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); @@ -1070,6 +1094,9 @@ var DrawingArea = new Lang.Class({ } }); +const MIN_REFLECTION_LINE_LENGTH = 10; // px +const INVERSION_CIRCLE_RADIUS = 12; // px + // DrawingElement represents a "brushstroke". // It can be converted into a cairo path as well as a svg element. // See DrawingArea._startDrawing() to know its params. @@ -1119,6 +1146,22 @@ const DrawingElement = new Lang.Class({ }, buildCairo: function(cr, params) { + let [success, color] = Clutter.Color.from_string(this.color); + if (success) + Clutter.cairo_set_source_color(cr, color); + + if (this.showSymmetryElement) { + let transformation = this.lastTransformation; + crSetDummyStroke(cr); + if (transformation.type == Transformations.REFLECTION) { + cr.moveTo(transformation.startX, transformation.startY); + cr.lineTo(transformation.endX, transformation.endY); + } else { + cr.arc(transformation.endX, transformation.endY, INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI); + } + cr.stroke(); + } + cr.setLineCap(this.line.lineCap); cr.setLineJoin(this.line.lineJoin); cr.setLineWidth(this.line.lineWidth); @@ -1134,11 +1177,8 @@ const DrawingElement = new Lang.Class({ else cr.setOperator(Cairo.Operator.OVER); - let [success, color] = Clutter.Color.from_string(this.color); - if (success) - Clutter.cairo_set_source_color(cr, color); if (SVG_DEBUG_SUPERPOSES_CAIRO) { - Clutter.cairo_set_source_color(cr, Clutter.Color.from_string("red")[1]); + Clutter.cairo_set_source_color(cr, Clutter.Color.new(255, 0, 0, 255)); cr.setLineWidth(this.line.lineWidth / 2 || 1); } @@ -1159,6 +1199,12 @@ const DrawingElement = new Lang.Class({ crRotate(cr, transformation.angle, center[0], center[1]); crScale(cr, transformation.scale, 1, center[0], center[1]); crRotate(cr, -transformation.angle, center[0], center[1]); + } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { + cr.translate(transformation.slideX, transformation.slideY); + cr.rotate(transformation.angle); + cr.scale(transformation.scaleX, transformation.scaleY); + cr.rotate(-transformation.angle); + cr.translate(-transformation.slideX, -transformation.slideY); } }); @@ -1256,7 +1302,7 @@ const DrawingElement = new Lang.Class({ attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; let transAttribute = ''; - // Do translations first. + // Do translations first, because it works this way. this.transformations.filter(transformation => transformation.type == Transformations.TRANSLATION) .forEach(transformation => { transAttribute += transAttribute ? ' ' : ' transform="'; @@ -1281,6 +1327,12 @@ const DrawingElement = new Lang.Class({ transAttribute += `translate(${-center[0] * (transformation.scale - 1)},0) `; transAttribute += `scale(${transformation.scale},1) `; transAttribute += `rotate(${-transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; + } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { + transAttribute += `translate(${transformation.slideX}, ${transformation.slideY}) `; + transAttribute += `rotate(${transformation.angle * 180 / Math.PI}) `; + transAttribute += `scale(${transformation.scaleX}, ${transformation.scaleY}) `; + transAttribute += `rotate(${-transformation.angle * 180 / Math.PI}) `; + transAttribute += `translate(${-transformation.slideX}, ${-transformation.slideY})`; } }); transAttribute += transAttribute ? '"' : ''; @@ -1428,6 +1480,16 @@ const DrawingElement = new Lang.Class({ this.transformations.push({ startX: startX, startY: startY, type: type, scaleX: 1, scaleY: 1 }); else if (type == Transformations.SCALE_ANGLE) this.transformations.push({ startX: startX, startY: startY, type: type, scale: 1, angle: 0 }); + else if (type == Transformations.REFLECTION) + this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type, + scaleX: 1, scaleY: 1, slideX: 0, slideY: 0, angle: 0 }); + else if (type == Transformations.INVERSION) + this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type, + scaleX: -1, scaleY: -1, slideX: startX, slideY: startY, + angle: Math.PI + Math.atan(startY / (startX || 1)) }); + + if (type == Transformations.REFLECTION || type == Transformations.INVERSION) + this.showSymmetryElement = true; }, updateTransformation: function(x, y) { @@ -1453,19 +1515,55 @@ const DrawingElement = new Lang.Class({ let center = this._getCenterWithSlideBefore(transformation); transformation.scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]); transformation.angle = getAngle(center[0], center[1], center[0] + 1, center[1], x, y); + } else if (transformation.type == Transformations.REFLECTION) { + [transformation.endX, transformation.endY] = [x, y]; + if (getNearness([transformation.startX, transformation.startY], [x, y], MIN_REFLECTION_LINE_LENGTH)) { + // do nothing to avoid jumps (no transformation at starting and locked transformation after) + } else if (Math.abs(y - transformation.startY) <= 5 && Math.abs(x - transformation.startX) >= 5) { + [transformation.scaleX, transformation.scaleY] = [1, -1]; + [transformation.slideX, transformation.slideY] = [0, transformation.startY]; + transformation.angle = Math.PI; + } else if (Math.abs(x - transformation.startX) <= 5 && Math.abs(y - transformation.startY) >= 5) { + [transformation.scaleX, transformation.scaleY] = [-1, 1]; + [transformation.slideX, transformation.slideY] = [transformation.startX, 0]; + transformation.angle = Math.PI; + } else if (x != transformation.startX) { + let tan = (y - transformation.startY) / (x - transformation.startX); + [transformation.scaleX, transformation.scaleY] = [1, -1]; + [transformation.slideX, transformation.slideY] = [0, transformation.startY - transformation.startX * tan]; + transformation.angle = Math.PI + Math.atan(tan); + } else if (y != transformation.startY) { + let tan = (x - transformation.startX) / (y - transformation.startY); + [transformation.scaleX, transformation.scaleY] = [-1, 1]; + [transformation.slideX, transformation.slideY] = [transformation.startX - transformation.startY * tan, 0]; + transformation.angle = Math.PI - Math.atan(tan); + } + } else if (transformation.type == Transformations.INVERSION) { + [transformation.endX, transformation.endY] = [x, y]; + [transformation.scaleX, transformation.scaleY] = [-1, -1]; + [transformation.slideX, transformation.slideY] = [x, y]; + transformation.angle = Math.PI + Math.atan(y / (x || 1)); } }, - stopTransformation: function(x, y) { + stopTransformation: function() { // Clean transformations let transformation = this.lastTransformation; - if (transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < 1 || + if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) + this.showSymmetryElement = false; + + if (transformation.type == Transformations.REFLECTION && + getNearness([transformation.startX, transformation.startY], [transformation.endX, transformation.endY], MIN_REFLECTION_LINE_LENGTH) || + transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < 1 || transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < Math.PI / 1000) { + this.transformations.pop(); } else { delete transformation.startX; delete transformation.startY; + delete transformation.endX; + delete transformation.endY; } }, diff --git a/extension.js b/extension.js index 606636c..cd35c2e 100644 --- a/extension.js +++ b/extension.js @@ -196,6 +196,7 @@ var AreaManager = new Lang.Class({ 'select-polyline-shape': () => this.activeArea.selectTool(Draw.Tools.POLYLINE), 'select-move-tool': () => this.activeArea.selectTool(Draw.Tools.MOVE), 'select-resize-tool': () => this.activeArea.selectTool(Draw.Tools.RESIZE), + 'select-mirror-tool': () => this.activeArea.selectTool(Draw.Tools.MIRROR), 'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea), 'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea), 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index c0ad135..c8d3bd4 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -71,6 +71,9 @@ msgstr "" msgid "Resize" msgstr "" +msgid "Mirror" +msgstr "" + msgid "Fill" msgstr "" @@ -191,6 +194,9 @@ msgstr "" msgid "Select resize" msgstr "" +msgid "Select mirror" +msgstr "" + msgid "Toggle fill/stroke" msgstr "" diff --git a/prefs.js b/prefs.js index 386a7dc..1ac439c 100644 --- a/prefs.js +++ b/prefs.js @@ -55,6 +55,7 @@ var INTERNAL_KEYBINDINGS = { 'select-text-shape': "Select text", 'select-move-tool': "Select move", 'select-resize-tool': "Select resize", + 'select-mirror-tool': "Select mirror", 'toggle-fill': "Toggle fill/stroke", '-separator-2': '', 'increment-line-width': "Increment line width", diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 7a60b580426718781d8919afad21711f8ffad2a7..409cec50e7908f645d9e0283b5b710d2f03ceeb1 100644 GIT binary patch literal 4140 zcmZu!Yitx%7#);KDUVj3ZK+z2M+=>83ls{0QlvsbiESDT8kJ6WZ+EAiompmP`vQrt zL=;IHW1>NcB|b1}0*OkLkWl{+VnR|AqZt1{q6z+x@IwQMK|JT~-I-bFCMW02*>Ar2 z?qlw~v*T&QH4WR>)UO=8H>YdvQ{EKt)GH%RWd0Y^+O@;rp$9arXuhW1!LOzahW4aJ zt^*u58B5wuTEx7Ru&kIE@`df0j_t(~t}&=Pwm$4+UEPbjLfE`lt63*W)9wcoNH0+g zo&n4PX5R%qb`CICVIFuv1Ph__sDxewR4F_Ht_Bt>ECEOHD6W@Hg5~g50JRFMz(gIO z12Lc;SPiTJnt)wHe@!d}v(IK}1KMHmXTYIdRln1xo(F#%eBU(m^LuALqEG!G{Bm$D z(ATh|i9Ypg_|4#_fU+y@fAp#6!cT#-z?VN9EvHY-{G;G=z_GUuT&GVx2mS>3Do|!0 z9HLJ>6aEeGKftNo#}Cq{o(f;Xpp*l@om}kDr=9`72D|}SRPmvVr)GRBxD%*+c8QFq zW_&kzKhRSn8kvWh^&AEt1x}v5T}Gdp{uuZZ;Pn$LFVm;4hJO+KE71S%nkxF#jK2Y% zGF{W2Td$v`Pdy)gIk*<6x>@DYr)GPb!8?J{k&Ut*YUb$%3n06B&nd=Jb6keNZvYql zBVW;{mUV)^1IByJ1@x)uPk^rir8f^OrB6-&7I=CI#_K}KtMsW$;g^GJfuH{98lg{J z2EP^D2~@TW%lSaf@lAn;f!aUoiy2SNI!D1LfOT6w-$^?K({BZL0>@9O@r9P-3wD9=7Z1(gIze58_#yBYz*zU0 zUgn`@o#Wsi=^uOZefrd_^BTBerl!4nuJbf~YR>a^@bf@^9irws)D3RHg7L!n?=2Y* z&HiZzKLZpsep*7GdI9_l_+{Yw*RfaVQ}aC?1%C{@edp>o^r_jd)8LE1q1R4-K%ZLn z8@L3ERC&YZZS<)*zSZDH;4!rhP&0okxEt7}d}{g`@F>uxd}{h*;Bnw_sW5D0(YWev6|&+zqH}Q-^D&4R^TZiH?06!mAD1>M z@M;qR8%0{!zHXVe&OAq$r2v^HG% zLYpR|OndUiM4yZHY)vG>=cw8<78+ExOs4UjWYQ9PuY+FcHPWUv9I3lEU$>Xg6Q*bM zSRx@ysp+Y8MZ(FIbx-~g%vLrepWC!785xX^q4H*JnIrH=l&@|~x~6Upm^3tS4}^^M@^g)1P2vK|ZJOd2;f~$Xs^? zab`3h`lVO6!j211^=c3ochYGCTP6EsviC|)J1$=7AYQ#k>h)R}?aS!6ZQ5%2Et|Ts z7}l1AHai-|E{Yp!+6H<13)_W5uZ#%(-`Cu_;T>cOk z$1Zv1$FW=E_W?d<(2<(PZ-a*cemh_*MT4-H18NZPNn>Wb$;LsbaZrISb~(f5GMyW; zpr2e7CtL%0f~gmscS6lOPc1#(K=Pge^3-}g+5V`eKB|t4e>nex?XoI2vblvKZMrTN z;QT8d)Eh5sm)y895W)YLSB>FSV}9RO-5|p`X3_Z7;nnKN;`!|qjb9U9t(h#I^C23) zHoRJ!k5|iFj}h-rx(+rm+{0gRUr%8>xi2KKSLI*0Aa1xYPBsveG58<-(G*@a<)0tz z4}R}P+LT8iT@g_jY&M*_m}_wlA>3 zS2Umr8WS6o2+@cb3=owx#Dx0Cpos}2Mr-s37)|hpq(3xZ4B|PncW0(cb&`|w&6#h$ z``yRf`_1MjG+Wn9SCLmaxFcsr?~qy%xcKs51DVfaN~`h;c-NhZGPO)mZsGc&6oRr% z`3Za(u zQ-ERs%_(;Q(|{SkOdtZx1CWM?`CwuJz`j%h3xO)&9-ume#juGb5=+4k6kr+dYb2I~ z!-(O&5UZdOt3#;yt+wQT+;hBUE4@kv{2Xxd%V{rAr(FvD5cn9-5k3DVb=vcwkAS}d z9_j!6B6Zp`p^t%Y0FO$YHuVw=QWUuMY3zCW)27}8-UhTtf7;adfcFEX*Y_@=KkYft zhruU+$D}`P`j3FefaB+GM(9tQ{^Q_3fzd8~K6TpEOG*@_0@x(u&}N({xE0teb=uTB z!2Lka-}P0DLz{69fsX-0FP?sfI_(PRqu`%`RvCvj)$d!7V`5^(vb>?HSP9z#7oKa&rT9+H5xiei>+d_@i~yX-A+BgFoi8 zW6Q^=)2@bo5qt%xd~&hZFWT(aIJjuKqFitfenNlR9EWmn9ncvS-uTd_-U{9h{CIqk z7l$_Ebbxi>$f;a>+SL2O2Y|z`?!CtNv>E3p_zW=q-N7ByX|rFW;7h>J)~a8q)8_bG z1uw@3pWka}bFWzez5ztcef{*O&2cNi#xN5&wf)FG>a>fYmxEUVqt6aa=blA-D)c6B z50HIm=PCNr=6vl3zYZ*{c-Ma@$CH-WbRM-Lx(n>uaI?;h}n)c2h2qE4H6j)E@%Z+*Vw zGa>~91u)M8*GeW0p>|oOt2*w0A>gyV&DPVJJ)Nb4M5=?y4Ef7)avKa8g=kLY#q#esRmN?7C1pkGJiv3tFvHW>t92%CMe0 z_iMjh_^%4?R^|PLtvN#F(WRS7`foJc=JoB_jIf&pRxi5}j#&>MUg5a)!Ciemu6)N9 zjy@<<*RqU)_xdO49}bsr+||L|>U>{{?mUy#Y@q^LLBCCzM?#=NNDC|>E)lg) zPq-;HZP{D|xct1~%XmNO%1>>C1oRps@UeK)szQfSs50=%0b(NT8U9I(Ehx(1Y)&_a4&F3Y@ zw^NJvByB5e=BMA!qf?HrZr(n=1^wQbNCYj&an2Z7M~N)Wjj+~>DRk6s#|pk*N5;1@ z!c9Yk3z$h(M(~enwd}&R=h{; z)g8T4H*|L(oOepLF9#xN>)sIf{ZEGTL(9FomCbd*8)4ti%d^GmGqI&I2RT>XI7|l-bdM`rFDbd3C5voVjO-9aV--(?~%_Z z`(DCv!XJpAL|Qj7o&K$y|+(#ET|E5NL^u?2f| z^=$A`fc1C{Fa;-KhpzfKI{3f0gYSH%&_9hPxhV7mgt%+AVSJbS&(*N+xzKP%(O z?~H3-wX@z?KOs-7yX{%S)iYSve&^6QwnL{UP-J)f2W87Pgi|psoB^RUf)_LSaY|bj zPKd&O`ArI(IELI$WdG6@_Ahz#`hIB{&)b=@j=--fOBhVH;6V9)mh_u!AN~Jxireaf faOZ3kI-1FnDd?f!j-Bsct{(c&`7op(2OrnJ;8?~t diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml index 36bb16a..3dd963c 100644 --- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml +++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml @@ -116,6 +116,11 @@ select resize tool select resize tool + + ["<Primary>c"] + select mirror tool + select mirror tool + KP_Add','plus']]]> increment the line width From 22ffa69f68b974a3ba25269c2ac18d6b0021483e Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 16:55:18 +0200 Subject: [PATCH 28/67] scale_directional transformation It combines former `SCALE` and `SCALE_ANGLE` transformations. --- draw.js | 53 ++++++++++++++++++----------------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/draw.js b/draw.js index d2a115d..0e3187b 100644 --- a/draw.js +++ b/draw.js @@ -66,7 +66,7 @@ const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5 const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; var Tools = Object.assign({}, Shapes, Manipulations); const TextStates = { WRITTEN: 0, DRAWING: 1, WRITING: 2 }; -const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, SCALE: 3, SCALE_ANGLE: 4, REFLECTION: 5, INVERSION: 6 }; +const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, SCALE_DIRECTIONAL: 3, REFLECTION: 4, INVERSION: 5 }; const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; @@ -477,7 +477,7 @@ var DrawingArea = new Lang.Class({ if (this.currentTool == Manipulations.MOVE) this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION); else if (this.currentTool == Manipulations.RESIZE) - this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.SCALE : Transformations.SCALE_PRESERVE); + this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.SCALE_DIRECTIONAL : Transformations.SCALE_PRESERVE); else if (this.currentTool == Manipulations.MIRROR) { this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION); this._redisplay(); @@ -508,8 +508,8 @@ var DrawingArea = new Lang.Class({ if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { this.transformingElement.stopTransformation(); - this.transformingElement.startTransformation(x, y, Transformations.SCALE); - } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE) { + this.transformingElement.startTransformation(x, y, Transformations.SCALE_DIRECTIONAL); + } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE_DIRECTIONAL) { this.transformingElement.stopTransformation(); this.transformingElement.startTransformation(x, y, Transformations.SCALE_PRESERVE); } @@ -1188,16 +1188,10 @@ const DrawingElement = new Lang.Class({ } else if (transformation.type == Transformations.ROTATION) { let center = this._getCenterWithSlideBefore(transformation); crRotate(cr, transformation.angle, center[0], center[1]); - } else if (transformation.type == Transformations.SCALE_PRESERVE) { - let center = this._getCenterWithSlideBefore(transformation); - crScale(cr, transformation.scale, transformation.scale, center[0], center[1]); - } else if (transformation.type == Transformations.SCALE) { - let center = this._getCenterWithSlideBefore(transformation); - crScale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); - } else if (transformation.type == Transformations.SCALE_ANGLE) { + } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.SCALE_DIRECTIONAL) { let center = this._getCenterWithSlideBefore(transformation); crRotate(cr, transformation.angle, center[0], center[1]); - crScale(cr, transformation.scale, 1, center[0], center[1]); + crScale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); crRotate(cr, -transformation.angle, center[0], center[1]); } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { cr.translate(transformation.slideX, transformation.slideY); @@ -1316,16 +1310,10 @@ const DrawingElement = new Lang.Class({ if (transformation.type == Transformations.ROTATION) { transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; - } else if (transformation.type == Transformations.SCALE_PRESERVE) { - transAttribute += `translate(${-center[0] * (transformation.scale - 1)},${-center[1] * (transformation.scale - 1)}) `; - transAttribute += `scale(${transformation.scale})`; - } else if (transformation.type == Transformations.SCALE) { - transAttribute += `translate(${-center[0] * (transformation.scaleX - 1)},${-center[1] * (transformation.scaleY - 1)}) `; - transAttribute += `scale(${transformation.scaleX},${transformation.scaleY})`; - } else if (transformation.type == Transformations.SCALE_ANGLE) { + } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.SCALE_DIRECTIONAL) { transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]}) `; - transAttribute += `translate(${-center[0] * (transformation.scale - 1)},0) `; - transAttribute += `scale(${transformation.scale},1) `; + transAttribute += `translate(${-center[0] * (transformation.scaleX - 1)},${-center[1] * (transformation.scaleY - 1)}) `; + transAttribute += `scale(${transformation.scaleX},${transformation.scaleY}) `; transAttribute += `rotate(${-transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { transAttribute += `translate(${transformation.slideX}, ${transformation.slideY}) `; @@ -1474,12 +1462,8 @@ const DrawingElement = new Lang.Class({ this.transformations.push({ startX: startX, startY: startY, type: type, slideX: 0, slideY: 0 }); else if (type == Transformations.ROTATION) this.transformations.push({ startX: startX, startY: startY, type: type, angle: 0 }); - else if (type == Transformations.SCALE_PRESERVE) - this.transformations.push({ startX: startX, startY: startY, type: type, scale: 1 }); - else if (type == Transformations.SCALE) - this.transformations.push({ startX: startX, startY: startY, type: type, scaleX: 1, scaleY: 1 }); - else if (type == Transformations.SCALE_ANGLE) - this.transformations.push({ startX: startX, startY: startY, type: type, scale: 1, angle: 0 }); + else if (type == Transformations.SCALE_PRESERVE || type == Transformations.SCALE_DIRECTIONAL) + this.transformations.push({ startX: startX, startY: startY, type: type, scaleX: 1, scaleY: 1, angle: 0 }); else if (type == Transformations.REFLECTION) this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type, scaleX: 1, scaleY: 1, slideX: 0, slideY: 0, angle: 0 }); @@ -1503,18 +1487,17 @@ const DrawingElement = new Lang.Class({ transformation.angle = getAngle(center[0], center[1], transformation.startX, transformation.startY, x, y); } else if (transformation.type == Transformations.SCALE_PRESERVE) { let center = this._getCenterWithSlideBefore(transformation); - transformation.scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]); - } else if (transformation.type == Transformations.SCALE) { + let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1; + [transformation.scaleX, transformation.scaleY] = [scale, scale]; + } else if (transformation.type == Transformations.SCALE_DIRECTIONAL) { let center = this._getCenterWithSlideBefore(transformation); let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY); let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(3 * Math.PI / 8); let horizontal = Math.abs(Math.cos(startAngle)) >= Math.cos(Math.PI / 8); - transformation.scaleX = vertical ? 1 : Math.abs((x - center[0]) / (transformation.startX - center[0])); - transformation.scaleY = horizontal ? 1 : Math.abs((y - center[1]) / (transformation.startY - center[1])); - } else if (transformation.type == Transformations.SCALE_ANGLE) { - let center = this._getCenterWithSlideBefore(transformation); - transformation.scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]); - transformation.angle = getAngle(center[0], center[1], center[0] + 1, center[1], x, y); + let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1; + transformation.scaleX = vertical ? 1 : scale; + transformation.scaleY = !vertical ? 1 : scale; + transformation.angle = vertical || horizontal ? 0 : getAngle(center[0], center[1], center[0] + 1, center[1], x, y); } else if (transformation.type == Transformations.REFLECTION) { [transformation.endX, transformation.endY] = [x, y]; if (getNearness([transformation.startX, transformation.startY], [x, y], MIN_REFLECTION_LINE_LENGTH)) { From a569eed4595a550ea65d2a0edeb79a63b6532b2f Mon Sep 17 00:00:00 2001 From: abakkk Date: Thu, 18 Jun 2020 00:57:54 +0200 Subject: [PATCH 29/67] elementFinder -> elementGrabber --- draw.js | 118 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/draw.js b/draw.js index 0e3187b..d32967d 100644 --- a/draw.js +++ b/draw.js @@ -157,9 +157,9 @@ var DrawingArea = new Lang.Class({ set currentTool(tool) { this._currentTool = tool; if (Object.values(Manipulations).indexOf(tool) != -1) - this._startElementFinder(); + this._startElementGrabber(); else - this._stopElementFinder(); + this._stopElementGrabber(); }, _redisplay: function() { @@ -228,11 +228,11 @@ var DrawingArea = new Lang.Class({ this.elements[i].points[2] == this.elements[i].points[1] || this.elements[i].points[2] == this.elements[i].points[0]); - this.elements[i].buildCairo(cr, { showTextRectangle: this.transformingElement && this.transformingElement == this.elements[i], - drawTextRectangle: this.finderPoint ? true : false }); + this.elements[i].buildCairo(cr, { showTextRectangle: this.grabbedElement && this.grabbedElement == this.elements[i], + drawTextRectangle: this.grabPoint ? true : false }); - if (this.finderPoint) - this._findTransformingElement(cr, this.elements[i]); + if (this.grabPoint) + this._searchElementToGrab(cr, this.elements[i]); if (this.elements[i].fill && !isStraightLine) { cr.fillPreserve(); @@ -304,7 +304,7 @@ var DrawingArea = new Lang.Class({ if (button == 1) { if (Object.values(Manipulations).indexOf(this.currentTool) != -1) { - if (this.transformingElement) + if (this.grabbedElement) this._startTransforming(x, y, controlPressed, shiftPressed); } else { this._startDrawing(x, y, shiftPressed); @@ -409,21 +409,21 @@ var DrawingArea = new Lang.Class({ return Clutter.EVENT_STOP; }, - _findTransformingElement: function(cr, element) { - if (element.getContainsPoint(cr, this.finderPoint[0], this.finderPoint[1])) - this.transformingElement = element; - else if (this.transformingElement == element) - this.transformingElement = null; + _searchElementToGrab: function(cr, element) { + if (element.getContainsPoint(cr, this.grabPoint[0], this.grabPoint[1])) + this.grabbedElement = element; + else if (this.grabbedElement == element) + this.grabbedElement = null; if (element == this.elements[this.elements.length - 1]) // All elements have been tested, the winner is the last. this.updatePointerCursor(); }, - _startElementFinder: function() { - this.elementFinderHandler = this.connect('motion-event', (actor, event) => { - if (this.motionHandler || this.transformingElementLocked) { - this.finderPoint = null; + _startElementGrabber: function() { + this.elementGrabberHandler = this.connect('motion-event', (actor, event) => { + if (this.motionHandler || this.grabbedElementLocked) { + this.grabPoint = null; return; } @@ -432,18 +432,18 @@ var DrawingArea = new Lang.Class({ if (!s) return; - this.finderPoint = [x, y]; - this.transformingElement = null; - // this._redisplay calls this._findTransformingElement. + this.grabPoint = [x, y]; + this.grabbedElement = null; + // this._redisplay calls this._searchElementToGrab. this._redisplay(); }); }, - _stopElementFinder: function() { - if (this.elementFinderHandler) { - this.disconnect(this.elementFinderHandler); - this.finderPoint = null; - this.elementFinderHandler = null; + _stopElementGrabber: function() { + if (this.elementGrabberHandler) { + this.disconnect(this.elementGrabberHandler); + this.grabPoint = null; + this.elementGrabberHandler = null; } }, @@ -454,14 +454,14 @@ var DrawingArea = new Lang.Class({ return; if (this.currentTool == Manipulations.MIRROR) { - this.transformingElementLocked = !this.transformingElementLocked; - if (this.transformingElementLocked) { + this.grabbedElementLocked = !this.grabbedElementLocked; + if (this.grabbedElementLocked) { this.updatePointerCursor(); return; } } - this.finderPoint = null; + this.grabPoint = null; this.buttonReleasedHandler = this.connect('button-release-event', (actor, event) => { this._stopTransforming(); @@ -469,17 +469,17 @@ var DrawingArea = new Lang.Class({ if (duplicate) { // deep cloning - let copy = new DrawingElement(JSON.parse(JSON.stringify(this.transformingElement))); + let copy = new DrawingElement(JSON.parse(JSON.stringify(this.grabbedElement))); this.elements.push(copy); - this.transformingElement = copy; + this.grabbedElement = copy; } if (this.currentTool == Manipulations.MOVE) - this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION); + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION); else if (this.currentTool == Manipulations.RESIZE) - this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.SCALE_DIRECTIONAL : Transformations.SCALE_PRESERVE); + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.SCALE_DIRECTIONAL : Transformations.SCALE_PRESERVE); else if (this.currentTool == Manipulations.MIRROR) { - this.transformingElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION); + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION); this._redisplay(); } @@ -498,31 +498,31 @@ var DrawingArea = new Lang.Class({ }, _updateTransforming: function(x, y, controlPressed) { - if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.TRANSLATION) { - this.transformingElement.stopTransformation(); - this.transformingElement.startTransformation(x, y, Transformations.ROTATION); - } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.ROTATION) { - this.transformingElement.stopTransformation(); - this.transformingElement.startTransformation(x, y, Transformations.TRANSLATION); + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.TRANSLATION) { + this.grabbedElement.stopTransformation(); + this.grabbedElement.startTransformation(x, y, Transformations.ROTATION); + } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.ROTATION) { + this.grabbedElement.stopTransformation(); + this.grabbedElement.startTransformation(x, y, Transformations.TRANSLATION); } - if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { - this.transformingElement.stopTransformation(); - this.transformingElement.startTransformation(x, y, Transformations.SCALE_DIRECTIONAL); - } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.SCALE_DIRECTIONAL) { - this.transformingElement.stopTransformation(); - this.transformingElement.startTransformation(x, y, Transformations.SCALE_PRESERVE); + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { + this.grabbedElement.stopTransformation(); + this.grabbedElement.startTransformation(x, y, Transformations.SCALE_DIRECTIONAL); + } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_DIRECTIONAL) { + this.grabbedElement.stopTransformation(); + this.grabbedElement.startTransformation(x, y, Transformations.SCALE_PRESERVE); } - if (controlPressed && this.transformingElement.lastTransformation.type == Transformations.REFLECTION) { - this.transformingElement.transformations.pop(); - this.transformingElement.startTransformation(x, y, Transformations.INVERSION); - } else if (!controlPressed && this.transformingElement.lastTransformation.type == Transformations.INVERSION) { - this.transformingElement.transformations.pop(); - this.transformingElement.startTransformation(x, y, Transformations.REFLECTION); + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.REFLECTION) { + this.grabbedElement.transformations.pop(); + this.grabbedElement.startTransformation(x, y, Transformations.INVERSION); + } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.INVERSION) { + this.grabbedElement.transformations.pop(); + this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION); } - this.transformingElement.updateTransformation(x, y); + this.grabbedElement.updateTransformation(x, y); this._redisplay(); }, @@ -536,9 +536,9 @@ var DrawingArea = new Lang.Class({ this.buttonReleasedHandler = null; } - this.transformingElement.stopTransformation(); - this.transformingElement = null; - this.transformingElementLocked = false; + this.grabbedElement.stopTransformation(); + this.grabbedElement = null; + this.grabbedElementLocked = false; this._redisplay(); }, @@ -671,10 +671,10 @@ var DrawingArea = new Lang.Class({ }, updatePointerCursor: function(controlPressed) { - if (this.currentTool == Manipulations.MIRROR && this.transformingElementLocked) + if (this.currentTool == Manipulations.MIRROR && this.grabbedElementLocked) this.setPointerCursor('CROSSHAIR'); else if (Object.values(Manipulations).indexOf(this.currentTool) != -1) - this.setPointerCursor(this.transformingElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); + this.setPointerCursor(this.grabbedElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING)) this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); else if (this.currentElement.shape != Shapes.NONE && controlPressed) @@ -889,9 +889,9 @@ var DrawingArea = new Lang.Class({ this.disconnect(this._onKeyboardPopupMenuHandler); this._onKeyboardPopupMenuHandler = null; } - if (this.elementFinderHandler) { - this.disconnect(this.elementFinderHandler); - this.elementFinderHandler = null; + if (this.elementGrabberHandler) { + this.disconnect(this.elementGrabberHandler); + this.elementGrabberHandler = null; } if (this.motionHandler) { this.disconnect(this.motionHandler); From a33f7b8324f9261063c101b7cca843c79ce84f5a Mon Sep 17 00:00:00 2001 From: abakkk Date: Thu, 18 Jun 2020 01:03:50 +0200 Subject: [PATCH 30/67] add isStraightLine method to elements --- draw.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/draw.js b/draw.js index d32967d..48daae2 100644 --- a/draw.js +++ b/draw.js @@ -223,10 +223,6 @@ var DrawingArea = new Lang.Class({ for (let i = 0; i < this.elements.length; i++) { cr.save(); - 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]); this.elements[i].buildCairo(cr, { showTextRectangle: this.grabbedElement && this.grabbedElement == this.elements[i], drawTextRectangle: this.grabPoint ? true : false }); @@ -234,7 +230,7 @@ var DrawingArea = new Lang.Class({ if (this.grabPoint) this._searchElementToGrab(cr, this.elements[i]); - if (this.elements[i].fill && !isStraightLine) { + if (this.elements[i].fill && !this.elements[i].isStraightLine) { cr.fillPreserve(); if (this.elements[i].shape == Shapes.NONE || this.elements[i].shape == Shapes.LINE) cr.closePath(); @@ -1276,11 +1272,10 @@ const 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; - let isStraightLine = this.shape == Shapes.LINE && (points.length < 3 || points[2] == points[1] || points[2] == points[0]); - let fill = this.fill && !isStraightLine; + let fill = this.fill && !this.isStraightLine; let attributes; - if (isStraightLine) + if (this.isStraightLine) attributes = `stroke="${color}" ` + `stroke-width="${this.line.lineWidth}" ` + `stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`; @@ -1386,6 +1381,10 @@ const DrawingElement = new Lang.Class({ return this.transformations[this.transformations.length - 1]; }, + get isStraightLine() { + return this.shape == Shapes.LINE && (this.points.length < 3 || this.points[2] == this.points[1] || this.points[2] == this.points[0]); + }, + smooth: function(i) { if (i < 2) return; From 5ca7a5e4a680b6ca778bf79f0104a3e6694814b6 Mon Sep 17 00:00:00 2001 From: abakkk Date: Thu, 18 Jun 2020 20:21:56 +0200 Subject: [PATCH 31/67] fix transformed center getter Since reflection and inversion introductions, compute center only with the slides of the traslations is not correct. * `_getCenter` -> `_getOriginalCenter` * `_getCenterWithSlide` -> `_getTransformedCenter` * Use pango matrices to compute the new center position since matrices from Cairo, Graphene, ..., are not available with GJS. * Stock original center and transformed center to not compute it numerous time. --- draw.js | 84 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/draw.js b/draw.js index 48daae2..29b86e7 100644 --- a/draw.js +++ b/draw.js @@ -29,6 +29,7 @@ const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Mainloop = imports.mainloop; +const PangoMatrix = imports.gi.Pango.Matrix; const Shell = imports.gi.Shell; const St = imports.gi.St; @@ -1182,10 +1183,10 @@ const DrawingElement = new Lang.Class({ if (transformation.type == Transformations.TRANSLATION) { cr.translate(transformation.slideX, transformation.slideY); } else if (transformation.type == Transformations.ROTATION) { - let center = this._getCenterWithSlideBefore(transformation); + let center = this._getTransformedCenter(transformation); crRotate(cr, transformation.angle, center[0], center[1]); } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.SCALE_DIRECTIONAL) { - let center = this._getCenterWithSlideBefore(transformation); + let center = this._getTransformedCenter(transformation); crRotate(cr, transformation.angle, center[0], center[1]); crScale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); crRotate(cr, -transformation.angle, center[0], center[1]); @@ -1301,7 +1302,7 @@ const DrawingElement = new Lang.Class({ .reverse() .forEach(transformation => { transAttribute += transAttribute ? ' ' : ' transform="'; - let center = this._getCenter(); + let center = this._getOriginalCenter(); if (transformation.type == Transformations.ROTATION) { transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; @@ -1418,7 +1419,7 @@ const DrawingElement = new Lang.Class({ if (points.length < 2) return; - let center = this._getCenter(); + let center = this._getOriginalCenter(); this.transformations[0] = { type: Transformations.ROTATION, angle: getAngle(center[0], center[1], points[points.length - 1][0], points[points.length - 1][1], x, y) }; } else if (this.shape == Shapes.ELLIPSE && transform) { @@ -1426,7 +1427,7 @@ const DrawingElement = new Lang.Class({ return; points[2] = [x, y]; - let center = this._getCenter(); + let center = this._getOriginalCenter(); this.transformations[0] = { type: Transformations.ROTATION, angle: getAngle(center[0], center[1], center[0] + 1, center[1], x, y) }; } else if (this.shape == Shapes.LINE && transform) { @@ -1482,14 +1483,14 @@ const DrawingElement = new Lang.Class({ transformation.slideX = x - transformation.startX; transformation.slideY = y - transformation.startY; } else if (transformation.type == Transformations.ROTATION) { - let center = this._getCenterWithSlideBefore(transformation); + let center = this._getTransformedCenter(transformation); transformation.angle = getAngle(center[0], center[1], transformation.startX, transformation.startY, x, y); } else if (transformation.type == Transformations.SCALE_PRESERVE) { - let center = this._getCenterWithSlideBefore(transformation); + let center = this._getTransformedCenter(transformation); let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1; [transformation.scaleX, transformation.scaleY] = [scale, scale]; } else if (transformation.type == Transformations.SCALE_DIRECTIONAL) { - let center = this._getCenterWithSlideBefore(transformation); + let center = this._getTransformedCenter(transformation); let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY); let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(3 * Math.PI / 8); let horizontal = Math.abs(Math.cos(startAngle)) >= Math.cos(Math.PI / 8); @@ -1549,43 +1550,54 @@ const DrawingElement = new Lang.Class({ } }, - _getTotalSlideBefore: function(transformation) { - let totalSlide = [0, 0]; - - this.transformations.slice(0, this.transformations.indexOf(transformation)) - .filter(transformation => transformation.type == Transformations.TRANSLATION) - .forEach(transformation => totalSlide = [totalSlide[0] + transformation.slideX, totalSlide[1] + transformation.slideY]); - - return totalSlide; - }, - // When rotating grouped lines, lineOffset is used to retrieve the rotation center of the first line. _getLineOffset: function() { return (this.lineIndex || 0) * Math.abs(this.points[1][1] - this.points[0][1]); }, // The figure rotation center before transformations (original). - _getCenter: function() { - let points = this.points; + _getOriginalCenter: function() { + if (!this._originalCenter) { + let points = this.points; + this._originalCenter = this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] : + this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[1], points[2]) : + this.shape == Shapes.TEXT && this.textWidth ? [Math.min(points[0][0], points[1][0]), + Math.max(points[0][1], points[1][1]) - this._getLineOffset()] : + points.length >= 3 ? getCentroid(points) : + getNaiveCenter(points); + } - return this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] : - this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[1], points[2]) : - this.shape == Shapes.TEXT && this.textWidth ? [Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]) - this._getLineOffset()] : - points.length >= 3 ? getCentroid(points) : - getNaiveCenter(points); + return this._originalCenter; }, - // The figure rotation center that takes previous translations into account. - _getCenterWithSlideBefore: function(transformation) { - let [center, totalSlide] = [this._getCenter(), this._getTotalSlideBefore(transformation)]; - return [center[0] + totalSlide[0], center[1] + totalSlide[1]]; - }, - - _getCenterWithSlide: function() { - if (this.transformations.length) - return this._getCenterWithSlideBefore(this.lastTransformation); - else - return this._getCenter(); + // The figure rotation center, whose position is affected by all transformations done before 'transformation'. + _getTransformedCenter: function(transformation) { + if (!transformation.elementTransformedCenter) { + let matrix = new PangoMatrix({ xx: 1, xy: 0, yx: 0, yy: 1, x0: 0, y0: 0 }); + + // Apply transformations to the matrice in reverse order + // because Pango multiply matrices by the left when applying a transformation + this.transformations.slice(0, this.transformations.indexOf(transformation)).reverse().forEach(transformation => { + if (transformation.type == Transformations.TRANSLATION) { + matrix.translate(transformation.slideX, transformation.slideY); + } else if (transformation.type == Transformations.ROTATION) { + // nothing, the center position is preserved. + } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.SCALE_DIRECTIONAL) { + // nothing, the center position is preserved. + } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { + matrix.translate(transformation.slideX, transformation.slideY); + matrix.rotate(-transformation.angle * 180 / Math.PI); + matrix.scale(transformation.scaleX, transformation.scaleY); + matrix.rotate(transformation.angle * 180 / Math.PI); + matrix.translate(-transformation.slideX, -transformation.slideY); + } + }); + + let originalCenter = this._getOriginalCenter(); + transformation.elementTransformedCenter = matrix.transform_point(originalCenter[0], originalCenter[1]); + } + + return transformation.elementTransformedCenter; }, _addPoint: function(x, y, smoothedStroke) { From 6f9b07b64d8e36be5fa04e2e7f4112ae341d0702 Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 01:18:16 +0200 Subject: [PATCH 32/67] Reduce computing by skipping the most 'getContains' calls without notable effect --- draw.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/draw.js b/draw.js index 29b86e7..17419ec 100644 --- a/draw.js +++ b/draw.js @@ -424,6 +424,10 @@ var DrawingArea = new Lang.Class({ return; } + // Reduce computing without notable effect. + if (Math.random() <= 0.75) + return; + let coords = event.get_coords(); let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); if (!s) From a84d71bd5e320515d5d9c0ec6d14d48c7f5fd412 Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 01:22:12 +0200 Subject: [PATCH 33/67] Remove unnecessary 'setDash' --- draw.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/draw.js b/draw.js index 17419ec..d50683e 100644 --- a/draw.js +++ b/draw.js @@ -256,7 +256,6 @@ var DrawingArea = new Lang.Class({ if (this.isInDrawingMode && this.hasGrid && this.gridGap && this.gridGap >= 1) { cr.save(); Clutter.cairo_set_source_color(cr, this.gridColor); - cr.setDash([], 0); let [gridX, gridY] = [this.gridGap, this.gridGap]; while (gridX < this.monitor.width) { @@ -1170,8 +1169,6 @@ const DrawingElement = new Lang.Class({ if (this.dash.active) cr.setDash(this.dash.array, this.dash.offset); - else - cr.setDash([], 0); if (this.eraser) cr.setOperator(Cairo.Operator.CLEAR); From 879fad367cd9ebd8fa603c19f7425d950a5f0be9 Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 01:24:42 +0200 Subject: [PATCH 34/67] SCALE_DIRECTIONAL -> STRETCH --- draw.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/draw.js b/draw.js index d50683e..8cbda75 100644 --- a/draw.js +++ b/draw.js @@ -67,7 +67,7 @@ const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5 const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; var Tools = Object.assign({}, Shapes, Manipulations); const TextStates = { WRITTEN: 0, DRAWING: 1, WRITING: 2 }; -const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, SCALE_DIRECTIONAL: 3, REFLECTION: 4, INVERSION: 5 }; +const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 }; const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; @@ -477,7 +477,7 @@ var DrawingArea = new Lang.Class({ if (this.currentTool == Manipulations.MOVE) this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION); else if (this.currentTool == Manipulations.RESIZE) - this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.SCALE_DIRECTIONAL : Transformations.SCALE_PRESERVE); + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.STRETCH : Transformations.SCALE_PRESERVE); else if (this.currentTool == Manipulations.MIRROR) { this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION); this._redisplay(); @@ -508,8 +508,8 @@ var DrawingArea = new Lang.Class({ if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.SCALE_DIRECTIONAL); - } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_DIRECTIONAL) { + this.grabbedElement.startTransformation(x, y, Transformations.STRETCH); + } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.STRETCH) { this.grabbedElement.stopTransformation(); this.grabbedElement.startTransformation(x, y, Transformations.SCALE_PRESERVE); } @@ -1186,7 +1186,7 @@ const DrawingElement = new Lang.Class({ } else if (transformation.type == Transformations.ROTATION) { let center = this._getTransformedCenter(transformation); crRotate(cr, transformation.angle, center[0], center[1]); - } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.SCALE_DIRECTIONAL) { + } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) { let center = this._getTransformedCenter(transformation); crRotate(cr, transformation.angle, center[0], center[1]); crScale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); @@ -1307,7 +1307,7 @@ const DrawingElement = new Lang.Class({ if (transformation.type == Transformations.ROTATION) { transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; - } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.SCALE_DIRECTIONAL) { + } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) { transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]}) `; transAttribute += `translate(${-center[0] * (transformation.scaleX - 1)},${-center[1] * (transformation.scaleY - 1)}) `; transAttribute += `scale(${transformation.scaleX},${transformation.scaleY}) `; @@ -1463,7 +1463,7 @@ const DrawingElement = new Lang.Class({ this.transformations.push({ startX: startX, startY: startY, type: type, slideX: 0, slideY: 0 }); else if (type == Transformations.ROTATION) this.transformations.push({ startX: startX, startY: startY, type: type, angle: 0 }); - else if (type == Transformations.SCALE_PRESERVE || type == Transformations.SCALE_DIRECTIONAL) + else if (type == Transformations.SCALE_PRESERVE || type == Transformations.STRETCH) this.transformations.push({ startX: startX, startY: startY, type: type, scaleX: 1, scaleY: 1, angle: 0 }); else if (type == Transformations.REFLECTION) this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type, @@ -1490,7 +1490,7 @@ const DrawingElement = new Lang.Class({ let center = this._getTransformedCenter(transformation); let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1; [transformation.scaleX, transformation.scaleY] = [scale, scale]; - } else if (transformation.type == Transformations.SCALE_DIRECTIONAL) { + } else if (transformation.type == Transformations.STRETCH) { let center = this._getTransformedCenter(transformation); let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY); let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(3 * Math.PI / 8); @@ -1583,7 +1583,7 @@ const DrawingElement = new Lang.Class({ matrix.translate(transformation.slideX, transformation.slideY); } else if (transformation.type == Transformations.ROTATION) { // nothing, the center position is preserved. - } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.SCALE_DIRECTIONAL) { + } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) { // nothing, the center position is preserved. } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { matrix.translate(transformation.slideX, transformation.slideY); From 1e4a847565ff0d38cf9bef65bbd353c51588469b Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 01:35:25 +0200 Subject: [PATCH 35/67] replace Mainloop by GLib --- draw.js | 14 +++++++------- prefs.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/draw.js b/draw.js index 8cbda75..d20859b 100644 --- a/draw.js +++ b/draw.js @@ -28,7 +28,6 @@ const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; -const Mainloop = imports.mainloop; const PangoMatrix = imports.gi.Pango.Matrix; const Shell = imports.gi.Shell; const St = imports.gi.St; @@ -52,6 +51,7 @@ const GS_VERSION = Config.PACKAGE_VERSION; const CAIRO_DEBUG_EXTENDS = false; const SVG_DEBUG_EXTENDS = false; const SVG_DEBUG_SUPERPOSES_CAIRO = false; +const TEXT_CURSOR_TIME = 600; // ms const ICON_DIR = Me.dir.get_child('data').get_child('icons'); const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path(); @@ -683,7 +683,7 @@ var DrawingArea = new Lang.Class({ _stopTextCursorTimeout: function() { if (this.textCursorTimeoutId) { - Mainloop.source_remove(this.textCursorTimeoutId); + GLib.source_remove(this.textCursorTimeoutId); this.textCursorTimeoutId = null; } this.textHasCursor = false; @@ -691,7 +691,7 @@ var DrawingArea = new Lang.Class({ _updateTextCursorTimeout: function() { this._stopTextCursorTimeout(); - this.textCursorTimeoutId = Mainloop.timeout_add(600, () => { + this.textCursorTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, TEXT_CURSOR_TIME, () => { this.textHasCursor = !this.textHasCursor; this._redisplay(); return GLib.SOURCE_CONTINUE; @@ -2106,7 +2106,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - Mainloop.timeout_add(0, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { for (let i in obj) { let text; if (targetProperty == 'currentFontFamilyId') @@ -2148,7 +2148,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - Mainloop.timeout_add(0, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { for (let i = 1; i < this.area.colors.length; i++) { let text = this.area.colors[i].to_string(); let iCaptured = i; @@ -2191,7 +2191,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - Mainloop.timeout_add(0, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { this._populateOpenDrawingSubMenu(); // small trick to prevent the menu from "jumping" on first opening item.menu.open(); @@ -2244,7 +2244,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - Mainloop.timeout_add(0, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { this._populateSaveDrawingSubMenu(); // small trick to prevent the menu from "jumping" on first opening item.menu.open(); diff --git a/prefs.js b/prefs.js index 1ac439c..b342cc2 100644 --- a/prefs.js +++ b/prefs.js @@ -20,10 +20,10 @@ * along with this program. If not, see . */ +const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; -const Mainloop = imports.mainloop; const Config = imports.misc.config; const ExtensionUtils = imports.misc.extensionUtils; @@ -114,7 +114,7 @@ function init() { function buildPrefsWidget() { let topStack = new TopStack(); let switcher = new Gtk.StackSwitcher({halign: Gtk.Align.CENTER, visible: true, stack: topStack}); - Mainloop.timeout_add(0, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { let window = topStack.get_toplevel(); window.resize(720,500); let headerBar = window.get_titlebar(); From 4d901db0807747724d7dc10d1a34ee20543836cf Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 02:48:23 +0200 Subject: [PATCH 36/67] use constants for extremum values --- draw.js | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/draw.js b/draw.js index d20859b..1c2ce76 100644 --- a/draw.js +++ b/draw.js @@ -244,7 +244,7 @@ var DrawingArea = new Lang.Class({ if (this.currentElement) { cr.save(); this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING }) + showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING }); if (this.currentElement.fill && this.currentElement.line.lineWidth == 0) crSetDummyStroke(cr); @@ -373,11 +373,7 @@ var DrawingArea = new Lang.Class({ } else if (this.currentElement && (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421)) { - // copy last point - let lastPoint = this.currentElement.points[this.currentElement.points.length - 1]; - let secondToLastPoint = this.currentElement.points[this.currentElement.points.length - 2]; - if (!getNearness(secondToLastPoint, lastPoint, 3)) - this.currentElement.points.push([lastPoint[0], lastPoint[1]]); + this.currentElement.addPolyPoint(); return Clutter.EVENT_STOP; } else if (event.get_key_symbol() == Clutter.KEY_Escape) { @@ -1094,8 +1090,13 @@ var DrawingArea = new Lang.Class({ } }); -const MIN_REFLECTION_LINE_LENGTH = 10; // px -const INVERSION_CIRCLE_RADIUS = 12; // px +const INVERSION_CIRCLE_RADIUS = 12; // px +const REFLECTION_TOLERANCE = 5; // px, to select vertical and horizontal directions +const STRETCH_TOLERANCE = Math.PI / 8; // rad, to select vertical and horizontal directions +const MIN_REFLECTION_LINE_LENGTH = 10; // px +const MIN_TRANSLATION_DISTANCE = 1; // px +const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad +const MIN_DRAWING_SIZE = 3; // px // DrawingElement represents a "brushstroke". // It can be converted into a cairo path as well as a svg element. @@ -1240,7 +1241,7 @@ const DrawingElement = new Lang.Class({ cr.showText(params.showTextCursor ? (this.text + "_") : this.text); if (!params.showTextCursor) - this.textWidth = cr.getCurrentPoint()[0] - Math.min(points[0][0], points[1][0]) + this.textWidth = cr.getCurrentPoint()[0] - Math.min(points[0][0], points[1][0]); if (params.showTextRectangle || params.drawTextRectangle) { cr.rectangle(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]), @@ -1399,6 +1400,14 @@ const DrawingElement = new Lang.Class({ } }, + // For polygons and polylines. + addPolyPoint: function() { + // copy last point + let [lastPoint, secondToLastPoint] = [this.points[this.points.length - 1], this.points[this.points.length - 2]]; + if (!getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE)) + this.points.push([lastPoint[0], lastPoint[1]]); + }, + startDrawing: function(startX, startY) { this.points.push([startX, startY]); @@ -1421,7 +1430,8 @@ const DrawingElement = new Lang.Class({ return; let center = this._getOriginalCenter(); - this.transformations[0] = { type: Transformations.ROTATION, angle: getAngle(center[0], center[1], points[points.length - 1][0], points[points.length - 1][1], x, y) }; + this.transformations[0] = { type: Transformations.ROTATION, + angle: getAngle(center[0], center[1], points[points.length - 1][0], points[points.length - 1][1], x, y) }; } else if (this.shape == Shapes.ELLIPSE && transform) { if (points.length < 2) @@ -1429,7 +1439,8 @@ const DrawingElement = new Lang.Class({ points[2] = [x, y]; let center = this._getOriginalCenter(); - this.transformations[0] = { type: Transformations.ROTATION, angle: getAngle(center[0], center[1], center[0] + 1, center[1], x, y) }; + this.transformations[0] = { type: Transformations.ROTATION, + angle: getAngle(center[0], center[1], center[0] + 1, center[1], x, y) }; } else if (this.shape == Shapes.LINE && transform) { if (points.length < 2) @@ -1453,9 +1464,13 @@ const DrawingElement = new Lang.Class({ if (this.shape != Shapes.NONE && this.points.length >= 2) { let lastPoint = this.points[this.points.length - 1]; let secondToLastPoint = this.points[this.points.length - 2]; - if (getNearness(secondToLastPoint, lastPoint, 3)) + if (getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE)) this.points.pop(); } + + if (this.transformations[0] && this.transformations[0].type == Transformations.ROTATION && + Math.abs(this.transformations[0].angle) < MIN_ROTATION_ANGLE) + this.transformations.shift(); }, startTransformation: function(startX, startY, type) { @@ -1493,8 +1508,8 @@ const DrawingElement = new Lang.Class({ } else if (transformation.type == Transformations.STRETCH) { let center = this._getTransformedCenter(transformation); let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY); - let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(3 * Math.PI / 8); - let horizontal = Math.abs(Math.cos(startAngle)) >= Math.cos(Math.PI / 8); + let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(Math.PI / 2 - STRETCH_TOLERANCE); + let horizontal = Math.abs(Math.cos(startAngle)) >= Math.cos(STRETCH_TOLERANCE); let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1; transformation.scaleX = vertical ? 1 : scale; transformation.scaleY = !vertical ? 1 : scale; @@ -1503,11 +1518,11 @@ const DrawingElement = new Lang.Class({ [transformation.endX, transformation.endY] = [x, y]; if (getNearness([transformation.startX, transformation.startY], [x, y], MIN_REFLECTION_LINE_LENGTH)) { // do nothing to avoid jumps (no transformation at starting and locked transformation after) - } else if (Math.abs(y - transformation.startY) <= 5 && Math.abs(x - transformation.startX) >= 5) { + } else if (Math.abs(y - transformation.startY) <= REFLECTION_TOLERANCE && Math.abs(x - transformation.startX) > REFLECTION_TOLERANCE) { [transformation.scaleX, transformation.scaleY] = [1, -1]; [transformation.slideX, transformation.slideY] = [0, transformation.startY]; transformation.angle = Math.PI; - } else if (Math.abs(x - transformation.startX) <= 5 && Math.abs(y - transformation.startY) >= 5) { + } else if (Math.abs(x - transformation.startX) <= REFLECTION_TOLERANCE && Math.abs(y - transformation.startY) > REFLECTION_TOLERANCE) { [transformation.scaleX, transformation.scaleY] = [-1, 1]; [transformation.slideX, transformation.slideY] = [transformation.startX, 0]; transformation.angle = Math.PI; @@ -1539,8 +1554,8 @@ const DrawingElement = new Lang.Class({ if (transformation.type == Transformations.REFLECTION && getNearness([transformation.startX, transformation.startY], [transformation.endX, transformation.endY], MIN_REFLECTION_LINE_LENGTH) || - transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < 1 || - transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < Math.PI / 1000) { + transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < MIN_TRANSLATION_DISTANCE || + transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < MIN_ROTATION_ANGLE) { this.transformations.pop(); } else { From f0b3876bac81e533bcc499a58aa4107362156f3b Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 15:52:12 +0200 Subject: [PATCH 37/67] fourth point for curves Third and fourth points are available by pressing `Ctrl` or `Enter`. --- draw.js | 91 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/draw.js b/draw.js index 1c2ce76..617eb7b 100644 --- a/draw.js +++ b/draw.js @@ -370,10 +370,21 @@ var DrawingArea = new Lang.Class({ this._redisplay(); return Clutter.EVENT_STOP; + } else if (this.currentElement && this.currentElement.shape == Shapes.LINE) { + if (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421 || event.get_key_symbol() == 65507) { + // 65507 is 'Ctrl' key alone + this.currentElement.addPoint(); + this.updatePointerCursor(true); + this._redisplay(); + return Clutter.EVENT_STOP; + } else { + return Clutter.EVENT_PROPAGATE; + } + } else if (this.currentElement && (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421)) { - this.currentElement.addPolyPoint(); + this.currentElement.addPoint(); return Clutter.EVENT_STOP; } else if (event.get_key_symbol() == Clutter.KEY_Escape) { @@ -1207,6 +1218,10 @@ const DrawingElement = new Lang.Class({ 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.LINE && points.length == 4) { + cr.moveTo(points[0][0], points[0][1]); + cr.curveTo(points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][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++) { @@ -1323,7 +1338,12 @@ const DrawingElement = new Lang.Class({ }); transAttribute += transAttribute ? '"' : ''; - if (this.shape == Shapes.LINE && points.length == 3) { + if (this.shape == Shapes.LINE && points.length == 4) { + row += ``; + + } else if (this.shape == Shapes.LINE && points.length == 3) { row += ``; @@ -1385,27 +1405,29 @@ const DrawingElement = new Lang.Class({ }, get isStraightLine() { - return this.shape == Shapes.LINE && (this.points.length < 3 || this.points[2] == this.points[1] || this.points[2] == this.points[0]); - }, - - smooth: function(i) { - if (i < 2) - return; - this.points[i-1] = [(this.points[i-2][0] + this.points[i][0]) / 2, (this.points[i-2][1] + this.points[i][1]) / 2]; + return this.shape == Shapes.LINE && this.points.length == 2; }, smoothAll: function() { for (let i = 0; i < this.points.length; i++) { - this.smooth(i); + this._smooth(i); } }, - // For polygons and polylines. - addPolyPoint: function() { - // copy last point - let [lastPoint, secondToLastPoint] = [this.points[this.points.length - 1], this.points[this.points.length - 2]]; - if (!getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE)) - this.points.push([lastPoint[0], lastPoint[1]]); + addPoint: function() { + if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) { + // copy last point + let [lastPoint, secondToLastPoint] = [this.points[this.points.length - 1], this.points[this.points.length - 2]]; + if (!getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE)) + this.points.push([lastPoint[0], lastPoint[1]]); + } else if (this.shape == Shapes.LINE) { + if (this.points.length == 2) { + this.points[2] = this.points[1]; + } else if (this.points.length == 3) { + this.points[3] = this.points[2]; + this.points[2] = this.points[1]; + } + } }, startDrawing: function(startX, startY) { @@ -1420,10 +1442,12 @@ const DrawingElement = new Lang.Class({ if (x == points[points.length - 1][0] && y == points[points.length - 1][1]) return; - transform = transform || this.transformations.length >= 1 || this.shape == Shapes.LINE && points.length == 3; + transform = transform || this.transformations.length >= 1; if (this.shape == Shapes.NONE) { - this._addPoint(x, y, transform); + points.push([x, y]); + if (transform) + this._smooth(points.length - 1); } else if ((this.shape == Shapes.RECTANGLE || this.shape == Shapes.TEXT || this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) && transform) { if (points.length < 2) @@ -1442,14 +1466,6 @@ const DrawingElement = new Lang.Class({ this.transformations[0] = { type: Transformations.ROTATION, angle: getAngle(center[0], center[1], center[0] + 1, center[1], x, y) }; - } else if (this.shape == Shapes.LINE && transform) { - if (points.length < 2) - return; - - if (points.length == 2) - points[2] = points[1]; - points[1] = [x, y]; - } else if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) { points[points.length - 1] = [x, y]; @@ -1576,7 +1592,8 @@ const DrawingElement = new Lang.Class({ if (!this._originalCenter) { let points = this.points; this._originalCenter = this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] : - this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[1], points[2]) : + this.shape == Shapes.LINE && points.length == 4 ? getCurveCenter(points[0], points[1], points[2], points[3]) : + this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[0], points[1], points[2]) : this.shape == Shapes.TEXT && this.textWidth ? [Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]) - this._getLineOffset()] : points.length >= 3 ? getCentroid(points) : @@ -1616,10 +1633,10 @@ const DrawingElement = new Lang.Class({ return transformation.elementTransformedCenter; }, - _addPoint: function(x, y, smoothedStroke) { - this.points.push([x, y]); - if (smoothedStroke) - this.smooth(this.points.length - 1); + _smooth: function(i) { + if (i < 2) + return; + this.points[i-1] = [(this.points[i-2][0] + this.points[i][0]) / 2, (this.points[i-2][1] + this.points[i][1]) / 2]; } }); @@ -1702,11 +1719,15 @@ control point: p0 ---- p1 ---- p2 ---- p3 (p2 is not on the curve) t: 0 ---- 1/3 ---- 2/3 ---- 1 */ -// Here, p0 = p1 and t = 2/3. -// If the curve has a symmetry axis, p(2/3) is truly a center (the intersection of the curve and the axis). +// If the curve has a symmetry axis, it is truly a center (the intersection of the curve and the axis). // In other cases, it is not a notable point, just a visual approximation. -const getCurveCenter = function(p1, p2, p3) { - return [(p1[0] + 6*p1[0] + 12*p2[0] + 8*p3[0]) / 27, (p1[1] + 6*p1[1] + 12*p2[1] + 8*p3[1]) / 27]; +const getCurveCenter = function(p0, p1, p2, p3) { + if (p0[0] == p1[0] && p0[1] == p1[1]) + // p0 = p1, t = 2/3 + return [(p1[0] + 6*p1[0] + 12*p2[0] + 8*p3[0]) / 27, (p1[1] + 6*p1[1] + 12*p2[1] + 8*p3[1]) / 27]; + else + // t = 1/2 + return [(p0[0] + 3*p1[0] + 3*p2[0] + p3[0]) / 8, (p0[1] + 3*p1[1] + 3*p2[1] + p3[1]) / 8]; }; const getAngle = function(xO, yO, xA, yA, xB, yB) { From ca943ddd9e0a472ce654846835d660e4b533a3ac Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 19 Jun 2020 20:58:23 +0200 Subject: [PATCH 38/67] Fix and polish transformation builds * Fix reflection and inversion SVG transformations. * Remove the Cairo wrappers (crRotate, crScale, crTranslate). * Cairo transformation builds are now similar to SVG transformation builds. --- draw.js | 101 +++++++++++++++++++++++++------------------------------- 1 file changed, 45 insertions(+), 56 deletions(-) diff --git a/draw.js b/draw.js index 617eb7b..d291a27 100644 --- a/draw.js +++ b/draw.js @@ -244,10 +244,8 @@ var DrawingArea = new Lang.Class({ if (this.currentElement) { cr.save(); this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING }); - - if (this.currentElement.fill && this.currentElement.line.lineWidth == 0) - crSetDummyStroke(cr); + showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING, + dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); cr.stroke(); cr.restore(); @@ -1101,6 +1099,7 @@ var DrawingArea = new Lang.Class({ } }); +const RADIAN = 180 / Math.PI; // degree const INVERSION_CIRCLE_RADIUS = 12; // px const REFLECTION_TOLERANCE = 5; // px, to select vertical and horizontal directions const STRETCH_TOLERANCE = Math.PI / 8; // rad, to select vertical and horizontal directions @@ -1164,7 +1163,7 @@ const DrawingElement = new Lang.Class({ if (this.showSymmetryElement) { let transformation = this.lastTransformation; - crSetDummyStroke(cr); + setDummyStroke(cr); if (transformation.type == Transformations.REFLECTION) { cr.moveTo(transformation.startX, transformation.startY); cr.lineTo(transformation.endX, transformation.endY); @@ -1187,6 +1186,9 @@ const DrawingElement = new Lang.Class({ else cr.setOperator(Cairo.Operator.OVER); + if (params.dummyStroke) + setDummyStroke(cr); + if (SVG_DEBUG_SUPERPOSES_CAIRO) { Clutter.cairo_set_source_color(cr, Clutter.Color.new(255, 0, 0, 255)); cr.setLineWidth(this.line.lineWidth / 2 || 1); @@ -1197,12 +1199,16 @@ const DrawingElement = new Lang.Class({ cr.translate(transformation.slideX, transformation.slideY); } else if (transformation.type == Transformations.ROTATION) { let center = this._getTransformedCenter(transformation); - crRotate(cr, transformation.angle, center[0], center[1]); + cr.translate(center[0], center[1]); + cr.rotate(transformation.angle); + cr.translate(-center[0], -center[1]); } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) { let center = this._getTransformedCenter(transformation); - crRotate(cr, transformation.angle, center[0], center[1]); - crScale(cr, transformation.scaleX, transformation.scaleY, center[0], center[1]); - crRotate(cr, -transformation.angle, center[0], center[1]); + cr.translate(center[0], center[1]); + cr.rotate(transformation.angle); + cr.scale(transformation.scaleX, transformation.scaleY); + cr.rotate(-transformation.angle); + cr.translate(-center[0], -center[1]); } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { cr.translate(transformation.slideX, transformation.slideY); cr.rotate(transformation.angle); @@ -1229,14 +1235,20 @@ const DrawingElement = new Lang.Class({ } } else if (shape == Shapes.ELLIPSE && points.length >= 2) { + let radius = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]); let ratio = 1; - if (points[2]) - ratio = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]) / Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]); - crScale(cr, ratio, 1, points[0][0], points[0][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); - crScale(cr, 1 / ratio, 1, points[0][0], points[0][1]); + if (points[2]) { + ratio = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]) / radius; + cr.translate(points[0][0], points[0][1]); + cr.scale(ratio, 1); + cr.translate(-points[0][0], -points[0][1]); + cr.arc(points[0][0], points[0][1], radius, 0, 2 * Math.PI); + cr.translate(points[0][0], points[0][1]); + cr.scale(1 / ratio, 1); + cr.translate(-points[0][0], -points[0][1]); + } else + cr.arc(points[0][0], points[0][1], radius, 0, 2 * Math.PI); } else if (shape == Shapes.RECTANGLE && points.length == 2) { cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]); @@ -1262,7 +1274,7 @@ const DrawingElement = new Lang.Class({ cr.rectangle(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]), this.textWidth, - Math.abs(points[1][1] - points[0][1])); if (params.showTextRectangle) - crSetDummyStroke(cr); + setDummyStroke(cr); else // Only draw the rectangle to find the element, not to show it. cr.setLineWidth(0); @@ -1309,30 +1321,27 @@ const DrawingElement = new Lang.Class({ attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; let transAttribute = ''; - // Do translations first, because it works this way. - this.transformations.filter(transformation => transformation.type == Transformations.TRANSLATION) - .forEach(transformation => { + this.transformations.slice(0).reverse().forEach(transformation => { transAttribute += transAttribute ? ' ' : ' transform="'; - transAttribute += `translate(${transformation.slideX},${transformation.slideY})`; - }); - this.transformations.filter(transformation => transformation.type != Transformations.TRANSLATION) - .reverse() - .forEach(transformation => { - transAttribute += transAttribute ? ' ' : ' transform="'; - let center = this._getOriginalCenter(); + let center = this._getTransformedCenter(transformation); - if (transformation.type == Transformations.ROTATION) { - transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; + if (transformation.type == Transformations.TRANSLATION) { + transAttribute += `translate(${transformation.slideX},${transformation.slideY})`; + } else if (transformation.type == Transformations.ROTATION) { + transAttribute += `translate(${center[0]},${center[1]}) `; + transAttribute += `rotate(${transformation.angle * RADIAN}) `; + transAttribute += `translate(${-center[0]},${-center[1]})`; } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) { - transAttribute += `rotate(${transformation.angle * 180 / Math.PI},${center[0]},${center[1]}) `; - transAttribute += `translate(${-center[0] * (transformation.scaleX - 1)},${-center[1] * (transformation.scaleY - 1)}) `; + transAttribute += `translate(${center[0]},${center[1]}) `; + transAttribute += `rotate(${transformation.angle * RADIAN}) `; transAttribute += `scale(${transformation.scaleX},${transformation.scaleY}) `; - transAttribute += `rotate(${-transformation.angle * 180 / Math.PI},${center[0]},${center[1]})`; + transAttribute += `rotate(${-transformation.angle * RADIAN}) `; + transAttribute += `translate(${-center[0]},${-center[1]})`; } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { transAttribute += `translate(${transformation.slideX}, ${transformation.slideY}) `; - transAttribute += `rotate(${transformation.angle * 180 / Math.PI}) `; + transAttribute += `rotate(${transformation.angle * RADIAN}) `; transAttribute += `scale(${transformation.scaleX}, ${transformation.scaleY}) `; - transAttribute += `rotate(${-transformation.angle * 180 / Math.PI}) `; + transAttribute += `rotate(${-transformation.angle * RADIAN}) `; transAttribute += `translate(${-transformation.slideX}, ${-transformation.slideY})`; } }); @@ -1619,9 +1628,9 @@ const DrawingElement = new Lang.Class({ // nothing, the center position is preserved. } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { matrix.translate(transformation.slideX, transformation.slideY); - matrix.rotate(-transformation.angle * 180 / Math.PI); + matrix.rotate(-transformation.angle * RADIAN); matrix.scale(transformation.scaleX, transformation.scaleY); - matrix.rotate(transformation.angle * 180 / Math.PI); + matrix.rotate(transformation.angle * RADIAN); matrix.translate(-transformation.slideX, -transformation.slideY); } }); @@ -1640,33 +1649,13 @@ const DrawingElement = new Lang.Class({ } }); -/* - Some Cairo utils -*/ - -const crSetDummyStroke = function(cr) { +const setDummyStroke = function(cr) { cr.setLineWidth(2); cr.setLineCap(0); cr.setLineJoin(0); cr.setDash([1, 2], 0); }; -const crRotate = function(cr, angle, x, y) { - if (angle == 0) - return; - cr.translate(x, y); - cr.rotate(angle); - cr.translate(-x, -y); -}; - -const crScale = function(cr, scaleX, scaleY, x, y) { - if (scaleX == 1 && scaleY == 1) - return; - cr.translate(x, y); - cr.scale(scaleX, scaleY); - cr.translate(-x, -y); -}; - /* Some geometric utils */ From 9c351b5724bab3d0e18974145374a2dae7c2bee9 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 20 Jun 2020 11:17:56 +0200 Subject: [PATCH 39/67] Rework of pref and osd texts --- draw.js | 14 +++++--- locale/draw-on-your-screen.pot | 64 +++++++++++++++++++++++----------- prefs.js | 29 +++++++++------ 3 files changed, 71 insertions(+), 36 deletions(-) diff --git a/draw.js b/draw.js index d291a27..5a6cfe7 100644 --- a/draw.js +++ b/draw.js @@ -371,6 +371,9 @@ var DrawingArea = new Lang.Class({ } else if (this.currentElement && this.currentElement.shape == Shapes.LINE) { if (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421 || event.get_key_symbol() == 65507) { // 65507 is 'Ctrl' key alone + if (this.currentElement.points.length == 2) + this.emit('show-osd', null, _("Press %s to get a fourth control point") + .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); this.currentElement.addPoint(); this.updatePointerCursor(true); this._redisplay(); @@ -462,6 +465,8 @@ var DrawingArea = new Lang.Class({ this.grabbedElementLocked = !this.grabbedElementLocked; if (this.grabbedElementLocked) { this.updatePointerCursor(); + let label = controlPressed ? _("Mark a point of symmetry") : _("Draw a line of symmetry"); + this.emit('show-osd', null, label, "", -1); return; } } @@ -580,7 +585,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.startDrawing(startX, startY); if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE) - this.emit('show-osd', null, _("Press %s\nto mark vertices").format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); + this.emit('show-osd', null, _("Press %s to mark vertices").format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); this.motionHandler = this.connect('motion-event', (actor, event) => { if (this.spaceKeyPressed) @@ -627,7 +632,7 @@ var DrawingArea = new Lang.Class({ // start writing this.currentElement.textState = TextStates.WRITING; this.currentElement.text = ''; - this.emit('show-osd', null, _("Type your text\nand press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); + this.emit('show-osd', null, _("Type your text and press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); @@ -804,7 +809,7 @@ var DrawingArea = new Lang.Class({ incrementLineWidth: function(increment) { this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0); - this.emit('show-osd', null, this.currentLineWidth + " " + _("px"), "", 2 * this.currentLineWidth); + this.emit('show-osd', null, _("%d px").format(this.currentLineWidth), "", 2 * this.currentLineWidth); }, toggleLineJoin: function() { @@ -1823,6 +1828,7 @@ var DrawingHelper = new Lang.Class({ let hbox = new St.BoxLayout({ vertical: false }); hbox.add_child(new St.Label({ text: _(Prefs.OTHER_SHORTCUTS[i].desc) })); hbox.add_child(new St.Label({ text: Prefs.OTHER_SHORTCUTS[i].shortcut, x_expand: true })); + hbox.get_children()[0].get_clutter_text().set_use_markup(true); this.vbox.add_child(hbox); } @@ -2089,7 +2095,7 @@ const DrawingMenu = new Lang.Class({ _addSliderItem: function(menu, target, targetProperty) { let item = new PopupMenu.PopupBaseMenuItem({ activate: false }); - let label = new St.Label({ text: target[targetProperty] + " " + _("px"), style_class: 'draw-on-your-screen-menu-slider-label' }); + let label = new St.Label({ text: _("%d px").format(target[targetProperty]), style_class: 'draw-on-your-screen-menu-slider-label' }); let slider = new Slider.Slider(target[targetProperty] / 50); if (GS_VERSION < '3.33.0') { diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index c8d3bd4..e96e603 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -86,16 +86,26 @@ msgstr "" msgid "Full line" msgstr "" -# %s is a key label -msgid "" -"Press %s\n" -"to mark vertices" +msgid "%d px" +msgstr "" + +msgid "Press %s to get a fourth control point" +msgstr "" + +msgid "Mark a point of symmetry" +msgstr "" + +msgid "Draw a line of symmetry" msgstr "" # %s is a key label msgid "" -"Type your text\n" -"and press %s" +"Press %s to mark vertices" +msgstr "" + +# %s is a key label +msgid "" +"Type your text and press %s" msgstr "" msgid "Screenshot" @@ -285,12 +295,6 @@ msgstr "" msgid "Center click" msgstr "" -msgid "Transform shape (when drawing)" -msgstr "" - -msgid "Rotate drawing (when moving)" -msgstr "" - msgid "Increment/decrement line width" msgstr "" @@ -304,20 +308,41 @@ msgstr "" msgid "%s … %s" msgstr "" -msgid "Select eraser (when starting drawing)" -msgstr "" - -msgid "Duplicate drawing (when starting moving)" +msgid "Ignore pointer movement" msgstr "" # %s is a key label msgid "%s held" msgstr "" -msgid "Ignore pointer movement" +msgid "Leave" msgstr "" -msgid "Leave" +msgid "Select eraser (when starting a drawing)" +msgstr "" + +msgid "Duplicate (when starting a transformation)" +msgstr "" + +msgid "Rotate rectangle, polygon, polyline, text area" +msgstr "" + +msgid "Extend circle to ellipse" +msgstr "" + +msgid "Curve line" +msgstr "" + +msgid "Smooth free drawing stroke" +msgstr "" + +msgid "Rotate (when moving)" +msgstr "" + +msgid "Stretch (when resizing)" +msgstr "" + +msgid "Inverse (when mirroring)" msgstr "" #: About page @@ -432,6 +457,3 @@ msgstr "" #msgid "Fantasy" #msgstr "" -#msgid "px" -#msgstr "" - diff --git a/prefs.js b/prefs.js index b342cc2..5e5c947 100644 --- a/prefs.js +++ b/prefs.js @@ -56,26 +56,27 @@ var INTERNAL_KEYBINDINGS = { 'select-move-tool': "Select move", 'select-resize-tool': "Select resize", 'select-mirror-tool': "Select mirror", - 'toggle-fill': "Toggle fill/stroke", '-separator-2': '', + 'toggle-fill': "Toggle fill/stroke", + 'toggle-fill-rule': "Change fill rule", + '-separator-3': '', 'increment-line-width': "Increment line width", 'decrement-line-width': "Decrement line width", 'increment-line-width-more': "Increment line width even more", 'decrement-line-width-more': "Decrement line width even more", 'toggle-linejoin': "Change linejoin", 'toggle-linecap': "Change linecap", - 'toggle-fill-rule': "Change fill rule", 'toggle-dash': "Dashed line", - '-separator-3': '', + '-separator-4': '', 'toggle-font-family': "Change font family (generic name)", 'toggle-font-weight': "Change font weight", 'toggle-font-style': "Change font style", - '-separator-4': '', + '-separator-5': '', 'toggle-panel-and-dock-visibility': "Hide panel and dock", 'toggle-background': "Add a drawing background", 'toggle-grid': "Add a grid overlay", 'toggle-square-area': "Square drawing area", - '-separator-5': '', + '-separator-6': '', 'open-previous-json': "Open previous drawing", 'open-next-json': "Open next drawing", 'save-as-json': "Save drawing", @@ -97,14 +98,20 @@ var OTHER_SHORTCUTS = [ { desc: "Draw", get shortcut() { return _("Left click"); } }, { desc: "Menu", get shortcut() { return _("Right click"); } }, { desc: "Toggle fill/stroke", get shortcut() { return _("Center click"); } }, - { desc: "Transform shape (when drawing)", shortcut: getKeyLabel('') }, - { desc: "Rotate drawing (when moving)", shortcut: getKeyLabel('') }, { desc: "Increment/decrement line width", get shortcut() { return _("Scroll"); } }, { desc: "Select color", get shortcut() { return _("%s … %s").format(getKeyLabel('1'), getKeyLabel('9')); } }, - { desc: "Select eraser (when starting drawing)", get shortcut() { return _("%s held").format(getKeyLabel('')); } }, - { desc: "Duplicate drawing (when starting moving)", get shortcut() { return _("%s held").format(getKeyLabel('')); } }, { desc: "Ignore pointer movement", get shortcut() { return _("%s held").format(getKeyLabel('space')); } }, - { desc: "Leave", shortcut: getKeyLabel('Escape') } + { desc: "Leave", shortcut: getKeyLabel('Escape') }, + { desc: "-separator-1", shortcut: "" }, + { desc: "Select eraser (while starting a drawing)", shortcut: "%s".format(getKeyLabel('')) }, + { desc: "Duplicate (while starting a transformation)", shortcut: "%s".format(getKeyLabel('')) }, + { desc: "Rotate rectangle, polygon, polyline, text area", shortcut: getKeyLabel('') }, + { desc: "Extend circle to ellipse", shortcut: getKeyLabel('') }, + { desc: "Curve line", shortcut: getKeyLabel('') }, + { desc: "Smooth free drawing stroke", shortcut: getKeyLabel('') }, + { desc: "Rotate (while moving)", shortcut: getKeyLabel('') }, + { desc: "Stretch (while resizing)", shortcut: getKeyLabel('') }, + { desc: "Inverse (while mirroring)", shortcut: getKeyLabel('') } ]; function init() { @@ -286,7 +293,7 @@ const PrefsPage = new GObject.Class({ continue; } let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN }); - let otherLabel = new Gtk.Label({ label: _(OTHER_SHORTCUTS[i].desc) }); + let otherLabel = new Gtk.Label({ label: _(OTHER_SHORTCUTS[i].desc), use_markup: true }); otherLabel.set_halign(1); let otherLabel2 = new Gtk.Label({ label: OTHER_SHORTCUTS[i].shortcut }); otherBox.pack_start(otherLabel, true, true, 4); From b364895c7d55d3bb21ba958b34d0c89070f2e864 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 20 Jun 2020 12:59:46 +0200 Subject: [PATCH 40/67] use showOsd method exclusively --- extension.js | 110 ++++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/extension.js b/extension.js index cd35c2e..ae4961f 100644 --- a/extension.js +++ b/extension.js @@ -39,6 +39,7 @@ const Draw = Me.imports.draw; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const GS_VERSION = Config.PACKAGE_VERSION; +const HIDE_TIMEOUT_LONG = 2500; // ms, default is 1500 ms // DRAWING_ACTION_MODE is a custom Shell.ActionMode var DRAWING_ACTION_MODE = Math.pow(2,14); @@ -314,6 +315,10 @@ var AreaManager = new Lang.Class({ if (this.hiddenList) this.togglePanelAndDockOpacity(); + setCursor('DEFAULT'); + if (!this.osdDisabled) + this.showOsd(null, this.leaveGicon, _("Leaving drawing mode")); + Main.popModal(this.activeArea); this.removeInternalKeybindings(); this.activeArea.reactive = false; @@ -324,11 +329,7 @@ var AreaManager = new Lang.Class({ Main.layoutManager._backgroundGroup.insert_child_above(activeContainer, Main.layoutManager._bgManagers[activeIndex].backgroundActor); if (!this.settings.get_boolean("drawing-on-desktop")) activeContainer.hide(); - - setCursor('DEFAULT'); - if (!this.osdDisabled) - Main.osdWindowManager.show(activeIndex, this.leaveGicon, _("Leaving drawing mode"), null); - } else { + } else { // avoid to deal with Meta changes (global.display/global.screen) let currentIndex = Main.layoutManager.monitors.indexOf(Main.layoutManager.currentMonitor); let activeContainer = this.areas[currentIndex].get_parent(); @@ -346,15 +347,8 @@ var AreaManager = new Lang.Class({ setCursor('POINTING_HAND'); this.osdDisabled = this.settings.get_boolean('osd-disabled'); - if (!this.osdDisabled) { - // increase OSD display time - let hideTimeoutSave = OsdWindow.HIDE_TIMEOUT; - try { OsdWindow.HIDE_TIMEOUT = 2000; } catch(e) { /* HIDE_TIMEOUT is not exported with 'var' */ } - let label = _("Press %s for help").format(this.activeArea.helper.helpKeyLabel) + "\n\n" + _("Entering drawing mode"); - Main.osdWindowManager.show(currentIndex, this.enterGicon, label, null); - Main.osdWindowManager._osdWindows[this.areas.indexOf(this.activeArea)]._label.get_clutter_text().set_use_markup(true); - try { OsdWindow.HIDE_TIMEOUT = hideTimeoutSave; } catch(e) {} - } + let label = _("Press %s for help").format(this.activeArea.helper.helpKeyLabel) + "\n\n" + _("Entering drawing mode"); + this.showOsd(null, this.enterGicon, label, null, null, true); } if (this.indicator) @@ -362,45 +356,61 @@ var AreaManager = new Lang.Class({ }, // use level -1 to set no level (null) - showOsd: function(emitter, iconName, label, color, level) { - if (this.osdDisabled) - return; + showOsd: function(emitter, icon, label, color, level, long) { let activeIndex = this.areas.indexOf(this.activeArea); - if (activeIndex != -1) { - let maxLevel; - if (level == -1) - level = null; - else if (level > 100) - maxLevel = 2; - - // GS 3.32- : bar from 0 to 100 - // GS 3.34+ : bar from 0 to 1 - if (level && GS_VERSION > '3.33.0') - level = level / 100; - - let icon = iconName && new Gio.ThemedIcon({ name: iconName }); - Main.osdWindowManager.show(activeIndex, icon || this.enterGicon, label, level, maxLevel); - Main.osdWindowManager._osdWindows[activeIndex]._label.get_clutter_text().set_use_markup(true); - - if (color) { - Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:${color};`); - Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:${color};`); - let osdColorChangedHandler = Main.osdWindowManager._osdWindows[activeIndex]._label.connect('notify::text', () => { - Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:;`); - Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:;`); - Main.osdWindowManager._osdWindows[activeIndex]._label.disconnect(osdColorChangedHandler); - }); - } - - if (level === 0) { - Main.osdWindowManager._osdWindows[activeIndex]._label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); - // the same label is shared by all GS OSD so the style must be removed after being used - let osdLabelChangedHandler = Main.osdWindowManager._osdWindows[activeIndex]._label.connect('notify::text', () => { - Main.osdWindowManager._osdWindows[activeIndex]._label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); - Main.osdWindowManager._osdWindows[activeIndex]._label.disconnect(osdLabelChangedHandler); - }); + if (activeIndex == -1 || this.osdDisabled) + return; + + let hideTimeoutSave; + if (long) + try { + hideTimeoutSave = OsdWindow.HIDE_TIMEOUT; + OsdWindow.HIDE_TIMEOUT = HIDE_TIMEOUT_LONG; + } catch(e) { + // HIDE_TIMEOUT is not exportable. + hideTimeoutSave = null; } + + let maxLevel; + if (level == -1) + level = null; + else if (level > 100) + maxLevel = 2; + + // GS 3.32- : bar from 0 to 100 + // GS 3.34+ : bar from 0 to 1 + if (level && GS_VERSION > '3.33.0') + level = level / 100; + + if (icon && typeof icon == 'string') + icon = new Gio.ThemedIcon({ name: icon }); + else if (!icon) + icon = this.enterGicon; + + Main.osdWindowManager.show(activeIndex, icon, label, level, maxLevel); + Main.osdWindowManager._osdWindows[activeIndex]._label.get_clutter_text().set_use_markup(true); + + if (color) { + Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:${color};`); + Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:${color};`); + let osdColorChangedHandler = Main.osdWindowManager._osdWindows[activeIndex]._label.connect('notify::text', () => { + Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:;`); + Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:;`); + Main.osdWindowManager._osdWindows[activeIndex]._label.disconnect(osdColorChangedHandler); + }); } + + if (level === 0) { + Main.osdWindowManager._osdWindows[activeIndex]._label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); + // the same label is shared by all GS OSD so the style must be removed after being used + let osdLabelChangedHandler = Main.osdWindowManager._osdWindows[activeIndex]._label.connect('notify::text', () => { + Main.osdWindowManager._osdWindows[activeIndex]._label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); + Main.osdWindowManager._osdWindows[activeIndex]._label.disconnect(osdLabelChangedHandler); + }); + } + + if (hideTimeoutSave) + OsdWindow.HIDE_TIMEOUT = hideTimeoutSave; }, removeAreas: function() { From 3c14f72f5656f933d257db6e558b710dbdf71eb5 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 20 Jun 2020 14:25:23 +0200 Subject: [PATCH 41/67] osdWindowConstraint Replace osdWindowConstraint by custom class. DO NOT Enforce a ratio of 1. --- extension.js | 75 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/extension.js b/extension.js index ae4961f..d497aae 100644 --- a/extension.js +++ b/extension.js @@ -355,7 +355,7 @@ var AreaManager = new Lang.Class({ this.indicator.sync(this.activeArea != null); }, - // use level -1 to set no level (null) + // Use level -1 to set no level through a signal. showOsd: function(emitter, icon, label, color, level, long) { let activeIndex = this.areas.indexOf(this.activeArea); if (activeIndex == -1 || this.osdDisabled) @@ -387,25 +387,51 @@ var AreaManager = new Lang.Class({ else if (!icon) icon = this.enterGicon; + let osdWindow = Main.osdWindowManager._osdWindows[activeIndex]; + + try { + if (!this.osdWindowConstraint) + this.osdWindowConstraint = new OsdWindowConstraint(); + + if (!osdWindow._box.get_constraint(this.osdWindowConstraint.constructor.name)) { + osdWindow._box.remove_constraint(osdWindow._boxConstraint); + osdWindow._box.add_constraint_with_name(this.osdWindowConstraint.constructor.name, this.osdWindowConstraint); + this.osdWindowConstraint._minSize = osdWindow._boxConstraint._minSize; + osdWindow._boxConstraintOld = osdWindow._boxConstraint; + osdWindow._boxConstraint = this.osdWindowConstraint; + let osdConstraintHandler = osdWindow._box.connect('notify::mapped', (box) => { + if (!box.mapped) { + osdWindow._boxConstraint = osdWindow._boxConstraintOld; + osdWindow._boxConstraint._minSize = this.osdWindowConstraint._minSize; + osdWindow._box.remove_constraint(this.osdWindowConstraint); + osdWindow._box.add_constraint(osdWindow._boxConstraint); + osdWindow._box.disconnect(osdConstraintHandler); + } + }); + } + } catch(e) { + logError(e); + } + Main.osdWindowManager.show(activeIndex, icon, label, level, maxLevel); - Main.osdWindowManager._osdWindows[activeIndex]._label.get_clutter_text().set_use_markup(true); + osdWindow._label.get_clutter_text().set_use_markup(true); if (color) { - Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:${color};`); - Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:${color};`); - let osdColorChangedHandler = Main.osdWindowManager._osdWindows[activeIndex]._label.connect('notify::text', () => { - Main.osdWindowManager._osdWindows[activeIndex]._icon.set_style(`color:;`); - Main.osdWindowManager._osdWindows[activeIndex]._label.set_style(`color:;`); - Main.osdWindowManager._osdWindows[activeIndex]._label.disconnect(osdColorChangedHandler); + osdWindow._icon.set_style(`color:${color};`); + osdWindow._label.set_style(`color:${color};`); + let osdColorChangedHandler = osdWindow._label.connect('notify::text', () => { + osdWindow._icon.set_style(`color:;`); + osdWindow._label.set_style(`color:;`); + osdWindow._label.disconnect(osdColorChangedHandler); }); } if (level === 0) { - Main.osdWindowManager._osdWindows[activeIndex]._label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); + osdWindow._label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); // the same label is shared by all GS OSD so the style must be removed after being used - let osdLabelChangedHandler = Main.osdWindowManager._osdWindows[activeIndex]._label.connect('notify::text', () => { - Main.osdWindowManager._osdWindows[activeIndex]._label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); - Main.osdWindowManager._osdWindows[activeIndex]._label.disconnect(osdLabelChangedHandler); + let osdLabelChangedHandler = osdWindow._label.connect('notify::text', () => { + osdWindow._label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); + osdWindow._label.disconnect(osdLabelChangedHandler); }); } @@ -462,7 +488,30 @@ var AreaManager = new Lang.Class({ } }); -var DrawingIndicator = new Lang.Class({ +// The same as the original, without forcing a ratio of 1. +const OsdWindowConstraint = new Lang.Class({ + Name: 'DrawOnYourScreenOsdWindowConstraint', + Extends: OsdWindow.OsdWindowConstraint, + + vfunc_update_allocation: function(actor, actorBox) { + // Clutter will adjust the allocation for margins, + // so add it to our minimum size + let minSize = this._minSize + actor.margin_top + actor.margin_bottom; + let [width, height] = actorBox.get_size(); + + // DO NOT Enforce a ratio of 1 + let newWidth = Math.ceil(Math.max(minSize, width, height)); + let newHeight = Math.ceil(Math.max(minSize, height)); + actorBox.set_size(newWidth, newHeight); + + // Recenter + let [x, y] = actorBox.get_origin(); + actorBox.set_origin(Math.ceil(x + width / 2 - newWidth / 2), + Math.ceil(y + height / 2 - newHeight / 2)); + } +}); + +const DrawingIndicator = new Lang.Class({ Name: 'DrawOnYourScreenIndicator', _init: function() { From 98832cec72fc0558d150e0717457dc6ddfb1ae1c Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 21 Jun 2020 01:08:45 +0200 Subject: [PATCH 42/67] toggle modeless / modal Keybinding to ungrab keyboard and mouse while drawing remains at the top. To be aware that this create some problems with the shell (DnD #1 and maybe more ?). related to #6, #9, #20, #24, #33 --- draw.js | 5 + extension.js | 96 ++++++++++++------ locale/draw-on-your-screen.pot | 3 + prefs.js | 3 +- schemas/gschemas.compiled | Bin 4140 -> 4192 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 + 6 files changed, 79 insertions(+), 33 deletions(-) diff --git a/draw.js b/draw.js index 5a6cfe7..2f1d793 100644 --- a/draw.js +++ b/draw.js @@ -691,6 +691,11 @@ var DrawingArea = new Lang.Class({ this.setPointerCursor('MOVE_OR_RESIZE_WINDOW'); }, + initPointerCursor: function() { + this.currentPointerCursorName = null; + this.updatePointerCursor(); + }, + _stopTextCursorTimeout: function() { if (this.textCursorTimeoutId) { GLib.source_remove(this.textCursorTimeoutId); diff --git a/extension.js b/extension.js index d497aae..31d2a54 100644 --- a/extension.js +++ b/extension.js @@ -86,6 +86,12 @@ var AreaManager = new Lang.Class({ Shell.ActionMode.ALL, this.eraseDrawing.bind(this)); + Main.wm.addKeybinding('toggle-modal', + this.settings, + Meta.KeyBindingFlags.NONE, + Shell.ActionMode.ALL, + this.toggleModal.bind(this)); + this.updateAreas(); this.monitorChangedHandler = Main.layoutManager.connect('monitors-changed', this.updateAreas.bind(this)); @@ -306,53 +312,78 @@ var AreaManager = new Lang.Class({ } }, - toggleDrawing: function() { - if (this.activeArea) { - let activeIndex = this.areas.indexOf(this.activeArea); - let activeContainer = this.activeArea.get_parent(); - let save = activeIndex == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing'); - - if (this.hiddenList) - this.togglePanelAndDockOpacity(); - - setCursor('DEFAULT'); - if (!this.osdDisabled) - this.showOsd(null, this.leaveGicon, _("Leaving drawing mode")); - - Main.popModal(this.activeArea); - this.removeInternalKeybindings(); - this.activeArea.reactive = false; - this.activeArea.leaveDrawingMode(save); - this.activeArea = null; - - activeContainer.get_parent().remove_actor(activeContainer); + toggleContainer: function() { + if (!this.activeArea) + return; + + let activeContainer = this.activeArea.get_parent(); + let activeIndex = this.areas.indexOf(this.activeArea); + + if (activeContainer.get_parent() == Main.uiGroup) { + Main.uiGroup.remove_actor(activeContainer); Main.layoutManager._backgroundGroup.insert_child_above(activeContainer, Main.layoutManager._bgManagers[activeIndex].backgroundActor); if (!this.settings.get_boolean("drawing-on-desktop")) activeContainer.hide(); } else { - // avoid to deal with Meta changes (global.display/global.screen) - let currentIndex = Main.layoutManager.monitors.indexOf(Main.layoutManager.currentMonitor); - let activeContainer = this.areas[currentIndex].get_parent(); - - activeContainer.get_parent().remove_actor(activeContainer); + Main.layoutManager._backgroundGroup.remove_actor(activeContainer); Main.uiGroup.add_child(activeContainer); - + } + }, + + toggleModal: function() { + if (!this.activeArea) + return; + + if (Main.actionMode & DRAWING_ACTION_MODE) { + Main.popModal(this.activeArea); + setCursor('DEFAULT'); + this.activeArea.reactive = false; + this.removeInternalKeybindings(); + } else { // add Shell.ActionMode.NORMAL to keep system keybindings enabled (e.g. Alt + F2 ...) - if (!Main.pushModal(this.areas[currentIndex], { actionMode: DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL })) - return; - this.activeArea = this.areas[currentIndex]; + if (!Main.pushModal(this.activeArea, { actionMode: DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL })) + return false; this.addInternalKeybindings(); this.activeArea.reactive = true; - this.activeArea.enterDrawingMode(); + this.activeArea.initPointerCursor(); + } + + return true; + }, + + toggleDrawing: function() { + if (this.activeArea) { + let activeIndex = this.areas.indexOf(this.activeArea); + let save = activeIndex == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing'); - setCursor('POINTING_HAND'); + this.showOsd(null, this.leaveGicon, _("Leaving drawing mode")); + this.activeArea.leaveDrawingMode(save); + if (this.hiddenList) + this.togglePanelAndDockOpacity(); + + if (Main.actionMode & DRAWING_ACTION_MODE) + this.toggleModal(); + this.toggleContainer(); + this.activeArea = null; + } else { + // avoid to deal with Meta changes (global.display/global.screen) + let currentIndex = Main.layoutManager.monitors.indexOf(Main.layoutManager.currentMonitor); + this.activeArea = this.areas[currentIndex]; + this.toggleContainer(); + if (!this.toggleModal()) { + this.toggleContainer(); + this.activeArea = null; + return; + } + + this.activeArea.enterDrawingMode(); this.osdDisabled = this.settings.get_boolean('osd-disabled'); let label = _("Press %s for help").format(this.activeArea.helper.helpKeyLabel) + "\n\n" + _("Entering drawing mode"); this.showOsd(null, this.enterGicon, label, null, null, true); } if (this.indicator) - this.indicator.sync(this.activeArea != null); + this.indicator.sync(Boolean(this.activeArea)); }, // Use level -1 to set no level through a signal. @@ -482,6 +513,7 @@ var AreaManager = new Lang.Class({ this.toggleDrawing(); Main.wm.removeKeybinding('toggle-drawing'); Main.wm.removeKeybinding('erase-drawing'); + Main.wm.removeKeybinding('toggle-modal'); this.removeAreas(); if (this.indicator) this.indicator.disable(); diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index e96e603..03ff099 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -163,6 +163,9 @@ msgstr "" msgid "Erase all drawings" msgstr "" +msgid "Toggle modeless/modal" +msgstr "" + #: INTERNAL_KEYBINDINGS msgid "Undo last brushstroke" diff --git a/prefs.js b/prefs.js index 5e5c947..b08a7aa 100644 --- a/prefs.js +++ b/prefs.js @@ -37,7 +37,8 @@ const MARGIN = 10; var GLOBAL_KEYBINDINGS = { 'toggle-drawing': "Enter/leave drawing mode", - 'erase-drawing': "Erase all drawings" + 'erase-drawing': "Erase all drawings", + 'toggle-modal': "Toggle modeless/modal" }; var INTERNAL_KEYBINDINGS = { diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 409cec50e7908f645d9e0283b5b710d2f03ceeb1..fda9879c7f963d5e881c849b17159b30b433f063 100644 GIT binary patch literal 4192 zcmZ`+YlszP82&6b@9UbadD&_+%cC`Cchyx_biJ0F6|5|VMWnN5zCCB$IrE)n=IqT2 zQGu2Q7DjfFj3AdV47!jKxcw+32=+&m)E|X}gpz&)Eku{+ne)wMFCBPzo_Xgz@AuC8 zUFMzbkDH!tx`EKY`QY6Zow-TdCV?kk8E7W+URJb;Bj6La2vJol#J~7GQ3Hc`R6GUV z3n(w2&AX~7vwlH3PFD5>()DfS`dP~}dky6p{i^I4e$JE9<$pzHO-Mp;+?#Z4u&3v8(zXHr2 zSl2{5HRBJ0KLl#r1AVkp&xCyl{1veK4fbL1Rbc4gp&8s$)K#$m z0$zj-uxP*x$419!09(%dW&(WxlY=^$APM*_ovfNJrDLl@Oj|%FLr(ujtkB8 zFbuv1ENty>rk$GkaKp_3<~MHGN;@^{oB_80v;W+`gm!A$+rcKVuW@@AhnjULfe!;> z;SYb)pPKfQ;IqUxFVCl)n&S?Ee+3pkxj38`>Tvylr_IE??5O*h{?zoZ2X_Kn`hFXx zoq86>1s?~ZGm4sL6xqT-GSp?yqdztC?*qRGY}Ik78RsPUbKumuNf3V zH2i{DlJHsI?%74t>u)Wg^cmA%7N~sQkw%wt1Eb3<+D<>bQhr_9PX{Zi9%%$hIiXj| zuc-ZO*RpeFpghB}eY4Y%miEkaF7r+N+4K#+Cm-jrzN27w1+6|ZXF3*g4y56jeqcZr zrGBMu3+-acxSai<-@${$@ob$(NqWBR<7F8bd{NqNKFLG**4XS&J>*v@m}4gei{m<@ z>t&gKfdS+D7|&J~)=#Ra{xfCM!xMYbjQ!lGp1KzMeC4L&_@l=uD$8W8;$dr zJIWZZ)xv8xEL!CO*$i0H?+#Q6|G@Kj(o#_TCI7F~dqCG)KXOSq{prU}{L7>B>9U=0 z>f?B(zHdo%Ae=5EjMHmdLBS|0kBe@C@gHg5(^2jWJTn)dFG$s_WV+HZOcyhxa@|Ic z?c1HUV+WOGndDU%ofikqnWf}@_xSt!lpWzCDmSSz^CF?H7*iqS{8dW8(YmW?t^uRl>oV zT13f?AH7|wLnqrEzf|LRsQYHi8du}TVXGKH+UJqZvyt+SztB5 zz8tL@4p;V7>M7Iv|5sbEsn3}OfR3xxvZxw;Hn+F|AGnf zMPcK32(&V}S{dalJ=2$|H(GjqOUyJgPOlWs6MyNrtCFi#QQUBQl|0!cJ?ZA8AN%K2 zv1sc3pAO6qu`zL*leo=MT>Te(y4ocq9v!cVr<|Y;Sd{VjsKe^yYIPJ>&s(RN>&|-$ zM{FgX#ItjPxcD$m$6b?Lt%>5MA93xN=Ubcjubs%hWsHAI;@=YaM|JM8RoSl`Wnt!V SUg3GRP4&81`kYMtT>k;n5a!DO literal 4140 zcmZu!Yitx%7#);KDUVj3ZK+z2M+=>83ls{0QlvsbiESDT8kJ6WZ+EAiompmP`vQrt zL=;IHW1>NcB|b1}0*OkLkWl{+VnR|AqZt1{q6z+x@IwQMK|JT~-I-bFCMW02*>Ar2 z?qlw~v*T&QH4WR>)UO=8H>YdvQ{EKt)GH%RWd0Y^+O@;rp$9arXuhW1!LOzahW4aJ zt^*u58B5wuTEx7Ru&kIE@`df0j_t(~t}&=Pwm$4+UEPbjLfE`lt63*W)9wcoNH0+g zo&n4PX5R%qb`CICVIFuv1Ph__sDxewR4F_Ht_Bt>ECEOHD6W@Hg5~g50JRFMz(gIO z12Lc;SPiTJnt)wHe@!d}v(IK}1KMHmXTYIdRln1xo(F#%eBU(m^LuALqEG!G{Bm$D z(ATh|i9Ypg_|4#_fU+y@fAp#6!cT#-z?VN9EvHY-{G;G=z_GUuT&GVx2mS>3Do|!0 z9HLJ>6aEeGKftNo#}Cq{o(f;Xpp*l@om}kDr=9`72D|}SRPmvVr)GRBxD%*+c8QFq zW_&kzKhRSn8kvWh^&AEt1x}v5T}Gdp{uuZZ;Pn$LFVm;4hJO+KE71S%nkxF#jK2Y% zGF{W2Td$v`Pdy)gIk*<6x>@DYr)GPb!8?J{k&Ut*YUb$%3n06B&nd=Jb6keNZvYql zBVW;{mUV)^1IByJ1@x)uPk^rir8f^OrB6-&7I=CI#_K}KtMsW$;g^GJfuH{98lg{J z2EP^D2~@TW%lSaf@lAn;f!aUoiy2SNI!D1LfOT6w-$^?K({BZL0>@9O@r9P-3wD9=7Z1(gIze58_#yBYz*zU0 zUgn`@o#Wsi=^uOZefrd_^BTBerl!4nuJbf~YR>a^@bf@^9irws)D3RHg7L!n?=2Y* z&HiZzKLZpsep*7GdI9_l_+{Yw*RfaVQ}aC?1%C{@edp>o^r_jd)8LE1q1R4-K%ZLn z8@L3ERC&YZZS<)*zSZDH;4!rhP&0okxEt7}d}{g`@F>uxd}{h*;Bnw_sW5D0(YWev6|&+zqH}Q-^D&4R^TZiH?06!mAD1>M z@M;qR8%0{!zHXVe&OAq$r2v^HG% zLYpR|OndUiM4yZHY)vG>=cw8<78+ExOs4UjWYQ9PuY+FcHPWUv9I3lEU$>Xg6Q*bM zSRx@ysp+Y8MZ(FIbx-~g%vLrepWC!785xX^q4H*JnIrH=l&@|~x~6Upm^3tS4}^^M@^g)1P2vK|ZJOd2;f~$Xs^? zab`3h`lVO6!j211^=c3ochYGCTP6EsviC|)J1$=7AYQ#k>h)R}?aS!6ZQ5%2Et|Ts z7}l1AHai-|E{Yp!+6H<13)_W5uZ#%(-`Cu_;T>cOk z$1Zv1$FW=E_W?d<(2<(PZ-a*cemh_*MT4-H18NZPNn>Wb$;LsbaZrISb~(f5GMyW; zpr2e7CtL%0f~gmscS6lOPc1#(K=Pge^3-}g+5V`eKB|t4e>nex?XoI2vblvKZMrTN z;QT8d)Eh5sm)y895W)YLSB>FSV}9RO-5|p`X3_Z7;nnKN;`!|qjb9U9t(h#I^C23) zHoRJ!k5|iFj}h-rx(+rm+{0gRUr%8>xi2KKSLI*0Aa1xYPBsveG58<-(G*@a<)0tz z4}R}P+erase drawing erase drawing + + ["<Primary><Alt><Super>d"] + toggle modeless/modal + toggle modeless/modal + ["<Primary>z"] undo From 0b8786eaa00b0ad1163da3384bafcfad24a88240 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 22 Jun 2020 11:56:34 +0200 Subject: [PATCH 43/67] Some fixes and polishes around modal toggling and area disabling. * Close menu before toggling modal (before because the menu changes `Main.actionMode`). * Hide the helper and update the grid display when toggling modal. * Signal `stop-drawing` -> `leave-drawing-mode` for consistence. * `area.disable` -> area `destroy` event handler. * `area.isInDrawingMode` -> `area.reactive`. --- draw.js | 47 ++++++++++++++++++++++++++++------------------- extension.js | 10 ++++++---- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/draw.js b/draw.js index 2f1d793..ae32951 100644 --- a/draw.js +++ b/draw.js @@ -120,11 +120,14 @@ var DrawingArea = new Lang.Class({ Name: 'DrawOnYourScreenDrawingArea', Extends: St.DrawingArea, Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] }, - 'stop-drawing': {} }, + 'leave-drawing-mode': {} }, _init: function(params, monitor, helper, loadPersistent) { this.parent({ style_class: 'draw-on-your-screen', name: params.name}); + this.connect('destroy', this._onDestroy.bind(this)); + this.reactiveHandler = this.connect('notify::reactive', this._onReactiveChanged.bind(this)); + this.settings = Convenience.getSettings(); this.monitor = monitor; this.helper = helper; @@ -151,6 +154,11 @@ var DrawingArea = new Lang.Class({ return this._menu; }, + closeMenu: function() { + if (this._menu) + this._menu.close(); + }, + get currentTool() { return this._currentTool; }, @@ -251,7 +259,7 @@ var DrawingArea = new Lang.Class({ cr.restore(); } - if (this.isInDrawingMode && this.hasGrid && this.gridGap && this.gridGap >= 1) { + if (this.reactive && this.hasGrid && this.gridGap && this.gridGap >= 1) { cr.save(); Clutter.cairo_set_source_color(cr, this.gridColor); @@ -392,7 +400,7 @@ var DrawingArea = new Lang.Class({ if (this.helper.visible) this.helper.hideHelp(); else - this.emit('stop-drawing'); + this.emit('leave-drawing-mode'); return Clutter.EVENT_STOP; } else { @@ -867,23 +875,33 @@ var DrawingArea = new Lang.Class({ this.helper.showHelp(); }, + // The area is reactive when it is modal. + _onReactiveChanged: function() { + if (this.hasGrid) + this._redisplay(); + if (this.helper.visible) + this.helper.hideHelp(); + }, + + _onDestroy: function() { + this.disconnect(this.reactiveHandler); + this.erase(); + if (this._menu) + this._menu.disable(); + }, + enterDrawingMode: function() { - this.isInDrawingMode = true; this.stageKeyPressedHandler = global.stage.connect('key-press-event', this._onStageKeyPressed.bind(this)); this.stageKeyReleasedHandler = global.stage.connect('key-release-event', this._onStageKeyReleased.bind(this)); this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this)); this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); this._onKeyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this)); this.scrollHandler = this.connect('scroll-event', this._onScroll.bind(this)); - this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null); - if (this.hasGrid) - // redisplay to show the grid before updating the style because _updateStyle is long. - this._redisplay(); + this.get_parent().set_background_color(this.reactive && this.hasBackground ? this.activeBackgroundColor : null); this._updateStyle(); }, leaveDrawingMode: function(save) { - this.isInDrawingMode = false; if (this.stageKeyPressedHandler) { global.stage.disconnect(this.stageKeyPressedHandler); this.stageKeyPressedHandler = null; @@ -921,17 +939,13 @@ var DrawingArea = new Lang.Class({ this.scrollHandler = null; } - if (this.helper.visible) - this.helper.hideHelp(); - this.currentElement = null; this._stopTextCursorTimeout(); this.currentTool = Shapes.NONE; this.dashedLine = false; this.fill = false; this._redisplay(); - if (this._menu) - this._menu.close(); + this.closeMenu(); this.get_parent().set_background_color(null); if (save) this.savePersistent(); @@ -1101,11 +1115,6 @@ var DrawingArea = new Lang.Class({ get drawingContentsHasChanged() { let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`; return contents != this.lastJsonContents; - }, - - disable: function() { - this.erase(); - this.menu.disable(); } }); diff --git a/extension.js b/extension.js index 31d2a54..d690b11 100644 --- a/extension.js +++ b/extension.js @@ -166,7 +166,7 @@ var AreaManager = new Lang.Class({ container.set_position(monitor.x, monitor.y); container.set_size(monitor.width, monitor.height); area.set_size(monitor.width, monitor.height); - area.stopDrawingHandler = area.connect('stop-drawing', this.toggleDrawing.bind(this)); + area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this)); area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this)); this.areas.push(area); } @@ -334,6 +334,9 @@ var AreaManager = new Lang.Class({ if (!this.activeArea) return; + // The menu changes Main.actionMode. + this.activeArea.closeMenu(); + if (Main.actionMode & DRAWING_ACTION_MODE) { Main.popModal(this.activeArea); setCursor('DEFAULT'); @@ -473,11 +476,10 @@ var AreaManager = new Lang.Class({ removeAreas: function() { for (let i = 0; i < this.areas.length; i++) { let area = this.areas[i]; + area.disconnect(area.leaveDrawingHandler); + area.disconnect(area.showOsdHandler); let container = area.get_parent(); container.get_parent().remove_actor(container); - area.disconnect(area.stopDrawingHandler); - area.disconnect(area.showOsdHandler); - area.disable(); container.destroy(); } this.areas = []; From 7f039398d0934c5d85c60622f086425c862fb62a Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 22 Jun 2020 12:16:17 +0200 Subject: [PATCH 44/67] Idlize savePersistent --- draw.js | 24 +++++++++++++----------- prefs.js | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/draw.js b/draw.js index ae32951..e6f737d 100644 --- a/draw.js +++ b/draw.js @@ -1029,13 +1029,15 @@ var DrawingArea = new Lang.Class({ if (name == Me.metadata['persistent-file-name'] && contents == oldContents) return; - GLib.file_set_contents(path, contents); - if (notify) - this.emit('show-osd', 'document-save-symbolic', name, "", -1); - if (name != Me.metadata['persistent-file-name']) { - this.jsonName = name; - this.lastJsonContents = contents; - } + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + GLib.file_set_contents(path, contents); + if (notify) + this.emit('show-osd', 'document-save-symbolic', name, "", -1); + if (name != Me.metadata['persistent-file-name']) { + this.jsonName = name; + this.lastJsonContents = contents; + } + }); }, saveAsJsonWithName: function(name) { @@ -2151,7 +2153,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { for (let i in obj) { let text; if (targetProperty == 'currentFontFamilyId') @@ -2193,7 +2195,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { for (let i = 1; i < this.area.colors.length; i++) { let text = this.area.colors[i].to_string(); let iCaptured = i; @@ -2236,7 +2238,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { this._populateOpenDrawingSubMenu(); // small trick to prevent the menu from "jumping" on first opening item.menu.open(); @@ -2289,7 +2291,7 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { this._populateSaveDrawingSubMenu(); // small trick to prevent the menu from "jumping" on first opening item.menu.open(); diff --git a/prefs.js b/prefs.js index b08a7aa..d092db8 100644 --- a/prefs.js +++ b/prefs.js @@ -122,7 +122,7 @@ function init() { function buildPrefsWidget() { let topStack = new TopStack(); let switcher = new Gtk.StackSwitcher({halign: Gtk.Align.CENTER, visible: true, stack: topStack}); - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { let window = topStack.get_toplevel(); window.resize(720,500); let headerBar = window.get_titlebar(); From 10384c06e69945989cae20c16eab22af2c389481 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 22 Jun 2020 12:45:54 +0200 Subject: [PATCH 45/67] Minor, key symbols --- draw.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/draw.js b/draw.js index e6f737d..e6ef807 100644 --- a/draw.js +++ b/draw.js @@ -346,26 +346,27 @@ var DrawingArea = new Lang.Class({ }, _onKeyPressed: function(actor, event) { + let [keySymbol, ctrlPressed] = [event.get_key_symbol(), event.has_control_modifier()]; + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { - if (event.get_key_symbol() == Clutter.KEY_Escape) { + if (keySymbol == Clutter.KEY_Escape) { // finish writing this._stopWriting(); - } else if (event.get_key_symbol() == Clutter.KEY_BackSpace) { + } else if (keySymbol == Clutter.KEY_BackSpace) { this.currentElement.text = this.currentElement.text.slice(0, -1); this._updateTextCursorTimeout(); - } else if (event.has_control_modifier() && event.get_key_symbol() == 118) { + } else if (ctrlPressed && keySymbol == Clutter.KEY_v) { // Ctrl + V St.Clipboard.get_default().get_text(St.ClipboardType.CLIPBOARD, (clipBoard, clipText) => { this.currentElement.text += clipText; this._updateTextCursorTimeout(); this._redisplay(); }); - } else if (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421) { - // Clutter.KEY_Return is "Enter" and 65421 is KP_Enter + } else if (keySymbol == Clutter.KEY_Return || keySymbol == Clutter.KEY_KP_Enter) { // start a new line let startNewLine = true; this._stopWriting(startNewLine); - } else if (event.has_control_modifier()){ + } else if (ctrlPressed){ // it is a shortcut, do not write text return Clutter.EVENT_PROPAGATE; } else { @@ -377,8 +378,7 @@ var DrawingArea = new Lang.Class({ return Clutter.EVENT_STOP; } else if (this.currentElement && this.currentElement.shape == Shapes.LINE) { - if (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421 || event.get_key_symbol() == 65507) { - // 65507 is 'Ctrl' key alone + if (keySymbol == Clutter.KEY_Return || keySymbol == Clutter.KEY_KP_Enter || keySymbol == Clutter.KEY_Control_L) { if (this.currentElement.points.length == 2) this.emit('show-osd', null, _("Press %s to get a fourth control point") .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); @@ -392,11 +392,11 @@ var DrawingArea = new Lang.Class({ } else if (this.currentElement && (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && - (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421)) { + (keySymbol == Clutter.KEY_Return || keySymbol == Clutter.KEY_KP_Enter)) { this.currentElement.addPoint(); return Clutter.EVENT_STOP; - } else if (event.get_key_symbol() == Clutter.KEY_Escape) { + } else if (keySymbol == Clutter.KEY_Escape) { if (this.helper.visible) this.helper.hideHelp(); else From 64507ba48310bfd1e429a3d33ecdb75e4a5fe0f8 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 22 Jun 2020 14:38:42 +0200 Subject: [PATCH 46/67] Fix menu * Populate 'Open' and 'Save' submenu on opening. It prevents the 'jump'. * By removing the previous trick, it now give the key-focus to the first item when opening the menu with 'Menu' key. --- draw.js | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/draw.js b/draw.js index e6ef807..a47e7d6 100644 --- a/draw.js +++ b/draw.js @@ -1965,7 +1965,7 @@ const DrawingMenu = new Lang.Class({ disable: function() { this.menuManager.removeMenu(this.menu); Main.layoutManager.uiGroup.remove_actor(this.menu.actor); - this.menu.actor.destroy(); + this.menu.destroy(); }, _onMenuOpenStateChanged: function(menu, open) { @@ -2238,13 +2238,13 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { - this._populateOpenDrawingSubMenu(); - // small trick to prevent the menu from "jumping" on first opening - item.menu.open(); - item.menu.close(); - return GLib.SOURCE_REMOVE; - }); + item.menu.openOld = item.menu.open; + item.menu.open = (animate) => { + if (!item.menu.isOpen) + this._populateOpenDrawingSubMenu(); + item.menu.openOld(); + } + menu.addMenuItem(item); }, @@ -2273,7 +2273,7 @@ const DrawingMenu = new Lang.Class({ deleteButton.connect('clicked', () => { file.delete(); - this._populateOpenDrawingSubMenu(); + item.destroy(); }); }); @@ -2291,13 +2291,12 @@ const DrawingMenu = new Lang.Class({ item.menu.close(); }; - GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { - this._populateSaveDrawingSubMenu(); - // small trick to prevent the menu from "jumping" on first opening - item.menu.open(); - item.menu.close(); - return GLib.SOURCE_REMOVE; - }); + item.menu.openOld = item.menu.open; + item.menu.open = (animate) => { + if (!item.menu.isOpen) + this._populateSaveDrawingSubMenu(); + item.menu.openOld(); + } menu.addMenuItem(item); }, @@ -2306,16 +2305,16 @@ const DrawingMenu = new Lang.Class({ }, _populateSaveDrawingSubMenu: function() { - this.saveEntry = new DrawingMenuEntry({ initialTextGetter: getDateString, + this.saveDrawingSubMenu.removeAll(); + let saveEntry = new DrawingMenuEntry({ initialTextGetter: getDateString, entryActivateCallback: (text) => { this.area.saveAsJsonWithName(text); this.saveDrawingSubMenu.toggle(); this._updateDrawingNameMenuItem(); - this._populateOpenDrawingSubMenu(); }, invalidStrings: [Me.metadata['persistent-file-name'], '/'], primaryIconName: 'insert-text' }); - this.saveDrawingSubMenu.addMenuItem(this.saveEntry.item); + this.saveDrawingSubMenu.addMenuItem(saveEntry.item); }, _addSeparator: function(menu) { From 579a6bfa325fbce708a319b09a9a1c97355212bd Mon Sep 17 00:00:00 2001 From: abakkk Date: Tue, 23 Jun 2020 08:46:28 +0200 Subject: [PATCH 47/67] Persistent attributes While attributes are not changed in user.css, they are persistent through drawing mode toggling. Css attributes that are modifiable in the user interface are just default attributes. Close #27 --- data/default.css | 3 ++- draw.js | 61 +++++++++++++++++++++++++----------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/data/default.css b/data/default.css index 466cd15..bae96a6 100644 --- a/data/default.css +++ b/data/default.css @@ -6,6 +6,7 @@ * * Except for the font, you don't need to restart the extension. * Just save this file as ~/.local/share/drawOnYourScreen/user.css and the changes will be applied for your next brushstroke. + * Some attributes are modifiable in the user interface. * * line-join (no string): * 0 : miter, 1 : round, 2 : bevel @@ -17,7 +18,7 @@ * dash: * By default, it is computed from the line width. * dash-array-on is the length of dashes (put 0.1 to get dots or squares according to line-cap). - * dash-array-off is the length of gaps + * dash-array-off is the length of gaps. * * square area: * Drawing in a square area is convenient when using the extension as a vector graphics editor. By default, diff --git a/draw.js b/draw.js index a47e7d6..784bf3e 100644 --- a/draw.js +++ b/draw.js @@ -74,7 +74,7 @@ const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; const FontWeightNames = { 0: 'Normal', 1: 'Bold' }; const FontStyleNames = { 0: 'Normal', 1: 'Italic', 2: 'Oblique' }; -const FontFamilyNames = { 0: 'Default', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; +const FontFamilyNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; const getDateString = function() { let date = GLib.DateTime.new_now_local(); @@ -136,6 +136,7 @@ var DrawingArea = new Lang.Class({ this.undoneElements = []; this.currentElement = null; this.currentTool = Shapes.NONE; + this.currentFontFamilyId = 0; this.isSquareArea = false; this.hasGrid = false; this.hasBackground = false; @@ -143,6 +144,8 @@ var DrawingArea = new Lang.Class({ this.dashedLine = false; this.fill = false; this.colors = [Clutter.Color.new(0, 0, 0, 255)]; + this.newThemeAttributes = {}; + this.oldThemeAttributes = {}; if (loadPersistent) this._loadPersistent(); @@ -182,23 +185,23 @@ var DrawingArea = new Lang.Class({ for (let i = 1; i < 10; i++) { this.colors[i] = themeNode.get_color('-drawing-color' + i); } - this.activeBackgroundColor = themeNode.get_color('-drawing-background-color'); - this.currentLineWidth = themeNode.get_length('-drawing-line-width'); - this.currentLineJoin = themeNode.get_double('-drawing-line-join'); - this.currentLineCap = themeNode.get_double('-drawing-line-cap'); - this.currentFillRule = themeNode.get_double('-drawing-fill-rule'); + let font = themeNode.get_font(); + this.newThemeAttributes.ThemeFontFamily = font.get_family(); + this.newThemeAttributes.FontWeight = font.get_weight(); + this.newThemeAttributes.FontStyle = font.get_style(); + this.newThemeAttributes.LineWidth = themeNode.get_length('-drawing-line-width'); + this.newThemeAttributes.LineJoin = themeNode.get_double('-drawing-line-join'); + this.newThemeAttributes.LineCap = themeNode.get_double('-drawing-line-cap'); + this.newThemeAttributes.FillRule = themeNode.get_double('-drawing-fill-rule'); this.dashArray = [Math.abs(themeNode.get_length('-drawing-dash-array-on')), Math.abs(themeNode.get_length('-drawing-dash-array-off'))]; this.dashOffset = themeNode.get_length('-drawing-dash-offset'); - let font = themeNode.get_font(); - this.fontFamily = font.get_family(); - this.currentFontWeight = font.get_weight(); - this.currentFontStyle = font.get_style(); - this.gridGap = themeNode.get_length('-grid-overlay-gap') || 10; - this.gridLineWidth = themeNode.get_length('-grid-overlay-line-width') || 0.4; - this.gridInterlineWidth = themeNode.get_length('-grid-overlay-interline-width') || 0.2; + this.gridGap = themeNode.get_length('-grid-overlay-gap'); + this.gridLineWidth = themeNode.get_length('-grid-overlay-line-width'); + this.gridInterlineWidth = themeNode.get_length('-grid-overlay-interline-width'); this.gridColor = themeNode.get_color('-grid-overlay-color'); this.squareAreaWidth = themeNode.get_length('-drawing-square-area-width'); this.squareAreaHeight = themeNode.get_length('-drawing-square-area-height'); + this.activeBackgroundColor = themeNode.get_color('-drawing-background-color'); } catch(e) { logError(e); } @@ -206,17 +209,20 @@ var DrawingArea = new Lang.Class({ for (let i = 1; i < 10; i++) { this.colors[i] = this.colors[i].alpha ? this.colors[i] : this.colors[0]; } - this.currentColor = this.colors[1]; - - this.currentLineWidth = (this.currentLineWidth > 0) ? this.currentLineWidth : 3; - this.currentLineJoin = ([0, 1, 2].indexOf(this.currentLineJoin) != -1) ? this.currentLineJoin : Cairo.LineJoin.ROUND; - this.currentLineCap = ([0, 1, 2].indexOf(this.currentLineCap) != -1) ? this.currentLineCap : Cairo.LineCap.ROUND; - this.currentFillRule = ([0, 1].indexOf(this.currentFillRule) != -1) ? this.currentFillRule : Cairo.FillRule.WINDING; - this.currentFontFamilyId = 0; - this.currentFontWeight = this.currentFontWeight > 500 ? 1 : 0 ; + this.currentColor = this.currentColor || this.colors[1]; + this.newThemeAttributes.LineWidth = (this.newThemeAttributes.LineWidth > 0) ? this.newThemeAttributes.LineWidth : 3; + this.newThemeAttributes.LineJoin = ([0, 1, 2].indexOf(this.newThemeAttributes.LineJoin) != -1) ? this.newThemeAttributes.LineJoin : Cairo.LineJoin.ROUND; + this.newThemeAttributes.LineCap = ([0, 1, 2].indexOf(this.newThemeAttributes.LineCap) != -1) ? this.newThemeAttributes.LineCap : Cairo.LineCap.ROUND; + this.newThemeAttributes.FillRule = ([0, 1].indexOf(this.newThemeAttributes.FillRule) != -1) ? this.newThemeAttributes.FillRule : Cairo.FillRule.WINDING; + this.newThemeAttributes.FontWeight = this.newThemeAttributes.FontWeight > 500 ? 1 : 0 ; // font style enum order of Cairo and Pango are different - this.currentFontStyle = this.currentFontStyle == 2 ? 1 : ( this.currentFontStyle == 1 ? 2 : 0); - + this.newThemeAttributes.FontStyle = this.newThemeAttributes.FontStyle == 2 ? 1 : ( this.newThemeAttributes.FontStyle == 1 ? 2 : 0); + for (const attributeName in this.newThemeAttributes) { + if (this.newThemeAttributes[attributeName] != this.oldThemeAttributes[attributeName]) { + this.oldThemeAttributes[attributeName] = this.newThemeAttributes[attributeName]; + this[`current${attributeName}`] = this.newThemeAttributes[attributeName]; + } + } this.gridGap = this.gridGap && this.gridGap >= 1 ? this.gridGap : 10; this.gridLineWidth = this.gridLineWidth || 0.4; this.gridInterlineWidth = this.gridInterlineWidth || 0.2; @@ -580,7 +586,7 @@ var DrawingArea = new Lang.Class({ eraser: eraser, 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 }, + font: { family: (this.currentFontFamilyId == 0 ? this.currentThemeFontFamily : FontFamilyNames[this.currentFontFamilyId]), weight: this.currentFontWeight, style: this.currentFontStyle }, points: [] }); @@ -860,7 +866,7 @@ var DrawingArea = new Lang.Class({ toggleFontFamily: function() { this.currentFontFamilyId = this.currentFontFamilyId == 5 ? 0 : this.currentFontFamilyId + 1; - let currentFontFamily = this.currentFontFamilyId == 0 ? this.fontFamily : FontFamilyNames[this.currentFontFamilyId]; + let currentFontFamily = this.currentFontFamilyId == 0 ? this.currentThemeFontFamily : FontFamilyNames[this.currentFontFamilyId]; if (this.currentElement) { this.currentElement.font.family = currentFontFamily; this._redisplay(); @@ -941,9 +947,6 @@ var DrawingArea = new Lang.Class({ this.currentElement = null; this._stopTextCursorTimeout(); - this.currentTool = Shapes.NONE; - this.dashedLine = false; - this.fill = false; this._redisplay(); this.closeMenu(); this.get_parent().set_background_color(null); @@ -2035,7 +2038,7 @@ const DrawingMenu = new Lang.Class({ let fontSection = new PopupMenu.PopupMenuSection(); let FontFamilyNamesCopy = Object.create(FontFamilyNames); - FontFamilyNamesCopy[0] = this.area.fontFamily; + FontFamilyNamesCopy[0] = this.area.currentThemeFontFamily; this._addSubMenuItem(fontSection, 'font-x-generic-symbolic', FontFamilyNamesCopy, this.area, 'currentFontFamilyId'); this._addSubMenuItem(fontSection, 'format-text-bold-symbolic', FontWeightNames, this.area, 'currentFontWeight'); this._addSubMenuItem(fontSection, 'format-text-italic-symbolic', FontStyleNames, this.area, 'currentFontStyle'); From 3c72b84a5c8a3a4b51844dbc5d46ef1d6e2b2f65 Mon Sep 17 00:00:00 2001 From: abakkk Date: Wed, 24 Jun 2020 09:18:24 +0200 Subject: [PATCH 48/67] Improve text shape * transform: translate * add rtl capacity (currently disabled) --- draw.js | 45 +++++++++++++++++++++++++++------- locale/draw-on-your-screen.pot | 5 +++- prefs.js | 3 ++- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/draw.js b/draw.js index 784bf3e..e97f4db 100644 --- a/draw.js +++ b/draw.js @@ -52,6 +52,7 @@ const CAIRO_DEBUG_EXTENDS = false; const SVG_DEBUG_EXTENDS = false; const SVG_DEBUG_SUPERPOSES_CAIRO = false; const TEXT_CURSOR_TIME = 600; // ms +const ENABLE_RTL = false; const ICON_DIR = Me.dir.get_child('data').get_child('icons'); const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path(); @@ -594,6 +595,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.fill = false; this.currentElement.text = _("Text"); this.currentElement.textState = TextStates.DRAWING; + this.currentElement.rtl = ENABLE_RTL && this.get_text_direction() == Clutter.TextDirection.RTL; } this.currentElement.startDrawing(startX, startY); @@ -1148,6 +1150,8 @@ const DrawingElement = new Lang.Class({ this.fillRule = Cairo.FillRule.WINDING; if (params.transformations === undefined) this.transformations = []; + if (params.shape == Shapes.TEXT && params.rtl === undefined) + this.rtl = false; if (params.transform && params.transform.center) { let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0); @@ -1175,6 +1179,7 @@ const DrawingElement = new Lang.Class({ transformations: this.transformations, text: this.text, lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined, + rtl: this.rtl, font: this.font, points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) }; @@ -1288,15 +1293,29 @@ const DrawingElement = new Lang.Class({ } else if (shape == Shapes.TEXT && points.length == 2) { 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(params.showTextCursor ? (this.text + "_") : this.text); + + let textWidth; + if (this.rtl) { + cr.save() + cr.setSourceRGBA(0, 0, 0, 0); + cr.moveTo(points[1][0], Math.max(points[0][1], points[1][1])); + cr.showText(params.showTextCursor ? ("_" + this.text) : this.text); + textWidth = cr.getCurrentPoint()[0] - points[1][0]; + cr.restore(); + cr.moveTo(points[1][0] - textWidth, Math.max(points[0][1], points[1][1])); + cr.showText(params.showTextCursor ? ("_" + this.text) : this.text); + } else { + cr.moveTo(points[1][0], Math.max(points[0][1], points[1][1])); + cr.showText(params.showTextCursor ? (this.text + "_") : this.text); + textWidth = cr.getCurrentPoint()[0] - points[1][0]; + } if (!params.showTextCursor) - this.textWidth = cr.getCurrentPoint()[0] - Math.min(points[0][0], points[1][0]); + this.textWidth = textWidth; if (params.showTextRectangle || params.drawTextRectangle) { - cr.rectangle(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]), - this.textWidth, - Math.abs(points[1][1] - points[0][1])); + cr.rectangle(points[1][0] - this.rtl * textWidth, Math.max(points[0][1], points[1][1]), + textWidth, - Math.abs(points[1][1] - points[0][1])); if (params.showTextRectangle) setDummyStroke(cr); else @@ -1424,7 +1443,8 @@ const DrawingElement = new Lang.Class({ `font-weight="${FontWeightNames[this.font.weight].toLowerCase()}" ` + `font-style="${FontStyleNames[this.font.style].toLowerCase()}"`; - row += `${this.text}`; + row += `${this.text}`; } return row; @@ -1482,7 +1502,7 @@ const DrawingElement = new Lang.Class({ if (transform) this._smooth(points.length - 1); - } else if ((this.shape == Shapes.RECTANGLE || this.shape == Shapes.TEXT || this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) && transform) { + } else if ((this.shape == Shapes.RECTANGLE || this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) && transform) { if (points.length < 2) return; @@ -1502,6 +1522,14 @@ const DrawingElement = new Lang.Class({ } else if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) { points[points.length - 1] = [x, y]; + } else if (this.shape == Shapes.TEXT && transform) { + if (points.length < 2) + return; + + let [slideX, slideY] = [x - points[1][0], y - points[1][1]]; + points[0] = [points[0][0] + slideX, points[0][1] + slideY]; + points[1] = [x, y]; + } else { points[1] = [x, y]; @@ -1627,8 +1655,7 @@ const DrawingElement = new Lang.Class({ this._originalCenter = this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] : this.shape == Shapes.LINE && points.length == 4 ? getCurveCenter(points[0], points[1], points[2], points[3]) : this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[0], points[1], points[2]) : - this.shape == Shapes.TEXT && this.textWidth ? [Math.min(points[0][0], points[1][0]), - Math.max(points[0][1], points[1][1]) - this._getLineOffset()] : + this.shape == Shapes.TEXT && this.textWidth ? [points[1][0], Math.max(points[0][1], points[1][1]) - this._getLineOffset()] : points.length >= 3 ? getCentroid(points) : getNaiveCenter(points); } diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 03ff099..7a2bec5 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -327,7 +327,10 @@ msgstr "" msgid "Duplicate (when starting a transformation)" msgstr "" -msgid "Rotate rectangle, polygon, polyline, text area" +msgid "Rotate rectangle, polygon, polyline" +msgstr "" + +msgid "Translate text area" msgstr "" msgid "Extend circle to ellipse" diff --git a/prefs.js b/prefs.js index d092db8..1f6cc25 100644 --- a/prefs.js +++ b/prefs.js @@ -106,7 +106,8 @@ var OTHER_SHORTCUTS = [ { desc: "-separator-1", shortcut: "" }, { desc: "Select eraser (while starting a drawing)", shortcut: "%s".format(getKeyLabel('')) }, { desc: "Duplicate (while starting a transformation)", shortcut: "%s".format(getKeyLabel('')) }, - { desc: "Rotate rectangle, polygon, polyline, text area", shortcut: getKeyLabel('') }, + { desc: "Rotate rectangle, polygon, polyline", shortcut: getKeyLabel('') }, + { desc: "Translate text area", shortcut: getKeyLabel('') }, { desc: "Extend circle to ellipse", shortcut: getKeyLabel('') }, { desc: "Curve line", shortcut: getKeyLabel('') }, { desc: "Smooth free drawing stroke", shortcut: getKeyLabel('') }, From b0c06b8a73b4644698a6c0baa9d352fbe368cc85 Mon Sep 17 00:00:00 2001 From: abakkk Date: Wed, 24 Jun 2020 23:16:22 +0200 Subject: [PATCH 49/67] Fix modal toggling criterion Use `Main._findModal` instead of `Main.actionMode` as the criterion of the toggle direction. Main.actionMode is not a safe criterion to know if the area is modal. For example, `Main.actionMode` is modified when toggling overview while drawing. --- extension.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension.js b/extension.js index d690b11..6cb2616 100644 --- a/extension.js +++ b/extension.js @@ -337,7 +337,7 @@ var AreaManager = new Lang.Class({ // The menu changes Main.actionMode. this.activeArea.closeMenu(); - if (Main.actionMode & DRAWING_ACTION_MODE) { + if (Main._findModal(this.activeArea) != -1) { Main.popModal(this.activeArea); setCursor('DEFAULT'); this.activeArea.reactive = false; @@ -364,7 +364,7 @@ var AreaManager = new Lang.Class({ if (this.hiddenList) this.togglePanelAndDockOpacity(); - if (Main.actionMode & DRAWING_ACTION_MODE) + if (Main._findModal(this.activeArea) != -1) this.toggleModal(); this.toggleContainer(); this.activeArea = null; From 5bf0ccc4f182e2c586b16d63f9f4737880f8fba0 Mon Sep 17 00:00:00 2001 From: abakkk Date: Thu, 25 Jun 2020 11:41:15 +0200 Subject: [PATCH 50/67] Use a St.Entry for text tool. All key events go through a hidden St Entry. * Ibus inputs support. * GS keyboard support, by moving the keyboard box above the drawing area. * All convenience keybindings of a St Entry (cursor moves, delete, paste, select all ...). * Add a writing action mode so some keybindings are disabled when writing (e.g. `Delete` and `Ctrl + A`). * Draw a thin rectangle as text cursor instead of a text character, so moving the cursor is smoother. Close #29, #34 --- draw.js | 206 ++++++++++++++++++++++++++++++++------------------- extension.js | 65 +++++++++++----- 2 files changed, 173 insertions(+), 98 deletions(-) diff --git a/draw.js b/draw.js index e97f4db..a4d79dd 100644 --- a/draw.js +++ b/draw.js @@ -29,7 +29,6 @@ const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const PangoMatrix = imports.gi.Pango.Matrix; -const Shell = imports.gi.Shell; const St = imports.gi.St; const BoxPointer = imports.ui.boxpointer; @@ -67,7 +66,6 @@ const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_pat const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; var Tools = Object.assign({}, Shapes, Manipulations); -const TextStates = { WRITTEN: 0, DRAWING: 1, WRITING: 2 }; const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 }; const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" }; const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; @@ -121,6 +119,7 @@ var DrawingArea = new Lang.Class({ Name: 'DrawOnYourScreenDrawingArea', Extends: St.DrawingArea, Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] }, + 'update-action-mode': {}, 'leave-drawing-mode': {} }, _init: function(params, monitor, helper, loadPersistent) { @@ -163,6 +162,10 @@ var DrawingArea = new Lang.Class({ this._menu.close(); }, + get isWriting() { + return this.textEntry ? true : false; + }, + get currentTool() { return this._currentTool; }, @@ -259,7 +262,7 @@ var DrawingArea = new Lang.Class({ if (this.currentElement) { cr.save(); this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showTextRectangle: this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING, + showTextRectangle: this.currentElement.shape == Shapes.TEXT && !this.isWriting, dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); cr.stroke(); @@ -300,14 +303,13 @@ var DrawingArea = new Lang.Class({ let controlPressed = event.has_control_modifier(); let shiftPressed = event.has_shift_modifier(); - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) // finish writing this._stopWriting(); - } if (this.helper.visible) { // hide helper - this.helper.hideHelp(); + this.toggleHelp(); return Clutter.EVENT_STOP; } @@ -333,7 +335,7 @@ var DrawingArea = new Lang.Class({ _onKeyboardPopupMenu: function() { this._stopDrawing(); if (this.helper.visible) - this.helper.hideHelp(); + this.toggleHelp(); this.menu.popup(); return Clutter.EVENT_STOP; }, @@ -353,39 +355,10 @@ var DrawingArea = new Lang.Class({ }, _onKeyPressed: function(actor, event) { - let [keySymbol, ctrlPressed] = [event.get_key_symbol(), event.has_control_modifier()]; - - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { - if (keySymbol == Clutter.KEY_Escape) { - // finish writing - this._stopWriting(); - } else if (keySymbol == Clutter.KEY_BackSpace) { - this.currentElement.text = this.currentElement.text.slice(0, -1); - this._updateTextCursorTimeout(); - } else if (ctrlPressed && keySymbol == Clutter.KEY_v) { - // Ctrl + V - St.Clipboard.get_default().get_text(St.ClipboardType.CLIPBOARD, (clipBoard, clipText) => { - this.currentElement.text += clipText; - this._updateTextCursorTimeout(); - this._redisplay(); - }); - } else if (keySymbol == Clutter.KEY_Return || keySymbol == Clutter.KEY_KP_Enter) { - // start a new line - let startNewLine = true; - this._stopWriting(startNewLine); - } else if (ctrlPressed){ - // it is a shortcut, do not write text - return Clutter.EVENT_PROPAGATE; - } else { - let unicode = event.get_key_unicode(); - this.currentElement.text += unicode; - this._updateTextCursorTimeout(); - } - this._redisplay(); - return Clutter.EVENT_STOP; - - } else if (this.currentElement && this.currentElement.shape == Shapes.LINE) { - if (keySymbol == Clutter.KEY_Return || keySymbol == Clutter.KEY_KP_Enter || keySymbol == Clutter.KEY_Control_L) { + if (this.currentElement && this.currentElement.shape == Shapes.LINE) { + if (event.get_key_symbol() == Clutter.KEY_Return || + event.get_key_symbol() == Clutter.KEY_KP_Enter || + event.get_key_symbol() == Clutter.KEY_Control_L) { if (this.currentElement.points.length == 2) this.emit('show-osd', null, _("Press %s to get a fourth control point") .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); @@ -399,13 +372,13 @@ var DrawingArea = new Lang.Class({ } else if (this.currentElement && (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && - (keySymbol == Clutter.KEY_Return || keySymbol == Clutter.KEY_KP_Enter)) { + (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) { this.currentElement.addPoint(); return Clutter.EVENT_STOP; - } else if (keySymbol == Clutter.KEY_Escape) { + } else if (event.get_key_symbol() == Clutter.KEY_Escape) { if (this.helper.visible) - this.helper.hideHelp(); + this.toggleHelp(); else this.emit('leave-drawing-mode'); return Clutter.EVENT_STOP; @@ -594,7 +567,6 @@ var DrawingArea = new Lang.Class({ if (this.currentTool == Shapes.TEXT) { this.currentElement.fill = false; this.currentElement.text = _("Text"); - this.currentElement.textState = TextStates.DRAWING; this.currentElement.rtl = ENABLE_RTL && this.get_text_direction() == Clutter.TextDirection.RTL; } @@ -644,15 +616,8 @@ var DrawingArea = new Lang.Class({ this.currentElement.stopDrawing(); if (this.currentElement && this.currentElement.points.length >= 2) { - if (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.DRAWING) { - // start writing - this.currentElement.textState = TextStates.WRITING; - this.currentElement.text = ''; - this.emit('show-osd', null, _("Type your text and press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); - this._updateTextCursorTimeout(); - this.textHasCursor = true; - this._redisplay(); - this.updatePointerCursor(); + if (this.currentElement.shape == Shapes.TEXT && !this.isWriting) { + this._startWriting(); return; } @@ -664,16 +629,65 @@ var DrawingArea = new Lang.Class({ this.updatePointerCursor(); }, - _stopWriting: function(startNewLine) { - this.currentElement.textState = TextStates.WRITTEN; + _startWriting: function() { + this.currentElement.text = ''; + this.currentElement.cursorPosition = 0; + this.emit('show-osd', null, _("Type your text and press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); + this._updateTextCursorTimeout(); + this.textHasCursor = true; + this._redisplay(); + this.updatePointerCursor(); + this.textEntry = new St.Entry({ visible: false }); + this.get_parent().add_child(this.textEntry); + this.textEntry.grab_key_focus(); + this.updateActionMode(); + + this.textEntry.clutterText.connect('activate', (clutterText) => { + let startNewLine = true; + this._stopWriting(startNewLine); + clutterText.text = ""; + }); + + this.textEntry.clutterText.connect('text-changed', (clutterText) => { + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.currentElement.text = clutterText.text; + this.currentElement.cursorPosition = clutterText.cursorPosition; + this._updateTextCursorTimeout(); + this._redisplay(); + }); + }); + + this.textEntry.clutterText.connect('key-press-event', (clutterText, event) => { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this._stopWriting(); + return Clutter.EVENT_STOP; + } + + // 'cursor-changed' signal is not emitted if the text entry is not visible. + // So key events related to the cursor must be listened. + if (event.get_key_symbol() == Clutter.KEY_Left || event.get_key_symbol() == Clutter.KEY_Right || + event.get_key_symbol() == Clutter.KEY_Home || event.get_key_symbol() == Clutter.KEY_End) { + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.currentElement.cursorPosition = clutterText.cursorPosition; + this._updateTextCursorTimeout(); + this.textHasCursor = true; + this._redisplay(); + }); + } + + return Clutter.EVENT_PROPAGATE; + }); + }, + + _stopWriting: function(startNewLine) { if (this.currentElement.text.length > 0) this.elements.push(this.currentElement); + if (startNewLine && this.currentElement.points.length == 2) { this.currentElement.lineIndex = this.currentElement.lineIndex || 0; // copy object, the original keep existing in this.elements this.currentElement = Object.create(this.currentElement); - this.currentElement.textState = TextStates.WRITING; this.currentElement.lineIndex ++; let height = Math.abs(this.currentElement.points[1][1] - this.currentElement.points[0][1]); // define a new 'points' array, the original keep existing in this.elements @@ -685,7 +699,12 @@ var DrawingArea = new Lang.Class({ } else { this.currentElement = null; this._stopTextCursorTimeout(); + this.textEntry.destroy(); + delete this.textEntry; + this.grab_key_focus(); + this.updateActionMode(); } + this._redisplay(); }, @@ -701,7 +720,7 @@ var DrawingArea = new Lang.Class({ this.setPointerCursor('CROSSHAIR'); else if (Object.values(Manipulations).indexOf(this.currentTool) != -1) this.setPointerCursor(this.grabbedElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); - else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING)) + else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.isWriting)) this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); else if (this.currentElement.shape != Shapes.NONE && controlPressed) this.setPointerCursor('MOVE_OR_RESIZE_WINDOW'); @@ -730,9 +749,9 @@ var DrawingArea = new Lang.Class({ }, erase: function() { + this.deleteLastElement(); this.elements = []; this.undoneElements = []; - this.currentElement = null; this._redisplay(); }, @@ -746,8 +765,9 @@ var DrawingArea = new Lang.Class({ this.disconnect(this.buttonReleasedHandler); this.buttonReleasedHandler = null; } + if (this.isWriting) + this._stopWriting(); this.currentElement = null; - this._stopTextCursorTimeout(); } else { this.elements.pop(); } @@ -877,10 +897,15 @@ var DrawingArea = new Lang.Class({ }, toggleHelp: function() { - if (this.helper.visible) + if (this.helper.visible) { this.helper.hideHelp(); - else + if (this.textEntry) + this.textEntry.grab_key_focus(); + } else { this.helper.showHelp(); + this.grab_key_focus(); + } + }, // The area is reactive when it is modal. @@ -888,7 +913,9 @@ var DrawingArea = new Lang.Class({ if (this.hasGrid) this._redisplay(); if (this.helper.visible) - this.helper.hideHelp(); + this.toggleHelp(); + if (this.textEntry && this.reactive) + this.textEntry.grab_key_focus(); }, _onDestroy: function() { @@ -898,6 +925,10 @@ var DrawingArea = new Lang.Class({ this._menu.disable(); }, + updateActionMode: function() { + this.emit('update-action-mode'); + }, + enterDrawingMode: function() { this.stageKeyPressedHandler = global.stage.connect('key-press-event', this._onStageKeyPressed.bind(this)); this.stageKeyReleasedHandler = global.stage.connect('key-release-event', this._onStageKeyReleased.bind(this)); @@ -958,7 +989,7 @@ var DrawingArea = new Lang.Class({ saveAsSvg: function() { // stop drawing or writing - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) { this._stopWriting(); } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { this._stopDrawing(); @@ -1002,7 +1033,7 @@ var DrawingArea = new Lang.Class({ _saveAsJson: function(name, notify) { // stop drawing or writing - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.textState == TextStates.WRITING) { + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) { this._stopWriting(); } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { this._stopDrawing(); @@ -1067,6 +1098,15 @@ var DrawingArea = new Lang.Class({ }, _loadJson: function(name, notify) { + // stop drawing or writing + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) { + this._stopWriting(); + } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { + this._stopDrawing(); + } + this.elements = []; + this.currentElement = null; + let dir = GLib.get_user_data_dir(); let path = GLib.build_filenamev([dir, Me.metadata['data-dir'], `${name}.json`]); @@ -1092,9 +1132,6 @@ var DrawingArea = new Lang.Class({ }, loadJson: function(name, notify) { - this.elements = []; - this.currentElement = null; - this._stopTextCursorTimeout(); this._loadJson(name, notify); this._redisplay(); }, @@ -1294,27 +1331,40 @@ const DrawingElement = new Lang.Class({ cr.selectFontFace(this.font.family, this.font.style, this.font.weight); cr.setFontSize(Math.abs(points[1][1] - points[0][1])); - let textWidth; + let textWidth = 0; + if (this.rtl) { - cr.save() + cr.save(); cr.setSourceRGBA(0, 0, 0, 0); + cr.setOperator(Cairo.Operator.OVER); cr.moveTo(points[1][0], Math.max(points[0][1], points[1][1])); - cr.showText(params.showTextCursor ? ("_" + this.text) : this.text); + cr.showText(this.text); textWidth = cr.getCurrentPoint()[0] - points[1][0]; cr.restore(); + } + + if (params.showTextCursor) { + let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition; + let texts = [this.text.slice(0, cursorPosition), this.text.slice(cursorPosition)]; cr.moveTo(points[1][0] - textWidth, Math.max(points[0][1], points[1][1])); - cr.showText(params.showTextCursor ? ("_" + this.text) : this.text); + cr.showText(texts[0]); + let currentPoint1 = cr.getCurrentPoint(); + cr.rectangle(currentPoint1[0], currentPoint1[1], Math.abs(points[1][1] - points[0][1]) / 25, - Math.abs(points[1][1] - points[0][1])); + cr.fill(); + cr.moveTo(currentPoint1[0], currentPoint1[1]); + cr.showText(texts[1]); + textWidth = this.rtl ? textWidth : (cr.getCurrentPoint()[0] - points[1][0]); } else { - cr.moveTo(points[1][0], Math.max(points[0][1], points[1][1])); - cr.showText(params.showTextCursor ? (this.text + "_") : this.text); - textWidth = cr.getCurrentPoint()[0] - points[1][0]; + cr.moveTo(points[1][0] - textWidth, Math.max(points[0][1], points[1][1])); + cr.showText(this.text); + textWidth = this.rtl ? textWidth : (cr.getCurrentPoint()[0] - points[1][0]); } if (!params.showTextCursor) this.textWidth = textWidth; if (params.showTextRectangle || params.drawTextRectangle) { - cr.rectangle(points[1][0] - this.rtl * textWidth, Math.max(points[0][1], points[1][1]), + cr.rectangle(points[1][0] - (this.rtl ? textWidth : 0), Math.max(points[0][1], points[1][1]), textWidth, - Math.abs(points[1][1] - points[0][1])); if (params.showTextRectangle) setDummyStroke(cr); @@ -2004,7 +2054,7 @@ const DrawingMenu = new Lang.Class({ } else { this.area.updatePointerCursor(); // actionMode has changed, set previous actionMode in order to keep internal shortcuts working - Main.actionMode = Extension.DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL; + this.area.updateActionMode(); this.area.grab_key_focus(); } }, @@ -2273,7 +2323,7 @@ const DrawingMenu = new Lang.Class({ if (!item.menu.isOpen) this._populateOpenDrawingSubMenu(); item.menu.openOld(); - } + }; menu.addMenuItem(item); }, @@ -2326,7 +2376,7 @@ const DrawingMenu = new Lang.Class({ if (!item.menu.isOpen) this._populateSaveDrawingSubMenu(); item.menu.openOld(); - } + }; menu.addMenuItem(item); }, diff --git a/extension.js b/extension.js index 6cb2616..26cefb5 100644 --- a/extension.js +++ b/extension.js @@ -41,8 +41,9 @@ const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const GS_VERSION = Config.PACKAGE_VERSION; const HIDE_TIMEOUT_LONG = 2500; // ms, default is 1500 ms -// DRAWING_ACTION_MODE is a custom Shell.ActionMode -var DRAWING_ACTION_MODE = Math.pow(2,14); +// custom Shell.ActionMode, assuming that they are unused +const DRAWING_ACTION_MODE = Math.pow(2,14); +const WRITING_ACTION_MODE = Math.pow(2,15); // use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900) var WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning'; @@ -167,24 +168,19 @@ var AreaManager = new Lang.Class({ container.set_size(monitor.width, monitor.height); area.set_size(monitor.width, monitor.height); area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this)); + area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this)); area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this)); this.areas.push(area); } }, addInternalKeybindings: function() { - this.internalKeybindings = { + // unavailable when writing + this.internalKeybindings1 = { 'undo': this.activeArea.undo.bind(this.activeArea), 'redo': this.activeArea.redo.bind(this.activeArea), 'delete-last-element': this.activeArea.deleteLastElement.bind(this.activeArea), 'smooth-last-element': this.activeArea.smoothLastElement.bind(this.activeArea), - 'save-as-svg': this.activeArea.saveAsSvg.bind(this.activeArea), - 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea), - 'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea), - 'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea), - 'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea), - 'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea), - 'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea), 'increment-line-width': () => this.activeArea.incrementLineWidth(1), 'decrement-line-width': () => this.activeArea.incrementLineWidth(-1), 'increment-line-width-more': () => this.activeArea.incrementLineWidth(5), @@ -203,7 +199,18 @@ var AreaManager = new Lang.Class({ 'select-polyline-shape': () => this.activeArea.selectTool(Draw.Tools.POLYLINE), 'select-move-tool': () => this.activeArea.selectTool(Draw.Tools.MOVE), 'select-resize-tool': () => this.activeArea.selectTool(Draw.Tools.RESIZE), - 'select-mirror-tool': () => this.activeArea.selectTool(Draw.Tools.MIRROR), + 'select-mirror-tool': () => this.activeArea.selectTool(Draw.Tools.MIRROR) + }; + + // available when writing + this.internalKeybindings2 = { + 'save-as-svg': this.activeArea.saveAsSvg.bind(this.activeArea), + 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea), + 'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea), + 'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea), + 'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea), + 'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea), + 'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea), 'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea), 'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea), 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), @@ -213,12 +220,20 @@ var AreaManager = new Lang.Class({ 'open-preferences': this.openPreferences.bind(this) }; - for (let key in this.internalKeybindings) { + for (let key in this.internalKeybindings1) { Main.wm.addKeybinding(key, this.settings, Meta.KeyBindingFlags.NONE, DRAWING_ACTION_MODE, - this.internalKeybindings[key]); + this.internalKeybindings1[key]); + } + + for (let key in this.internalKeybindings2) { + Main.wm.addKeybinding(key, + this.settings, + Meta.KeyBindingFlags.NONE, + DRAWING_ACTION_MODE | WRITING_ACTION_MODE, + this.internalKeybindings2[key]); } for (let i = 1; i < 10; i++) { @@ -226,19 +241,20 @@ var AreaManager = new Lang.Class({ Main.wm.addKeybinding('select-color' + i, this.settings, Meta.KeyBindingFlags.NONE, - DRAWING_ACTION_MODE, + DRAWING_ACTION_MODE | WRITING_ACTION_MODE, () => this.activeArea.selectColor(iCaptured)); } }, removeInternalKeybindings: function() { - for (let key in this.internalKeybindings) { + for (let key in this.internalKeybindings1) Main.wm.removeKeybinding(key); - } - for (let i = 1; i < 10; i++) { + for (let key in this.internalKeybindings2) + Main.wm.removeKeybinding(key); + + for (let i = 1; i < 10; i++) Main.wm.removeKeybinding('select-color' + i); - } }, openPreferences: function() { @@ -320,6 +336,7 @@ var AreaManager = new Lang.Class({ let activeIndex = this.areas.indexOf(this.activeArea); if (activeContainer.get_parent() == Main.uiGroup) { + Main.uiGroup.set_child_at_index(Main.layoutManager.keyboardBox, this.oldKeyboardIndex); Main.uiGroup.remove_actor(activeContainer); Main.layoutManager._backgroundGroup.insert_child_above(activeContainer, Main.layoutManager._bgManagers[activeIndex].backgroundActor); if (!this.settings.get_boolean("drawing-on-desktop")) @@ -327,6 +344,9 @@ var AreaManager = new Lang.Class({ } else { Main.layoutManager._backgroundGroup.remove_actor(activeContainer); Main.uiGroup.add_child(activeContainer); + // move the keyboard above the area to make it available with text entries + this.oldKeyboardIndex = Main.uiGroup.get_children().indexOf(Main.layoutManager.keyboardBox); + Main.uiGroup.set_child_above_sibling(Main.layoutManager.keyboardBox, activeContainer); } }, @@ -334,7 +354,6 @@ var AreaManager = new Lang.Class({ if (!this.activeArea) return; - // The menu changes Main.actionMode. this.activeArea.closeMenu(); if (Main._findModal(this.activeArea) != -1) { @@ -344,7 +363,8 @@ var AreaManager = new Lang.Class({ this.removeInternalKeybindings(); } else { // add Shell.ActionMode.NORMAL to keep system keybindings enabled (e.g. Alt + F2 ...) - if (!Main.pushModal(this.activeArea, { actionMode: DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL })) + let actionMode = (this.activeArea.isWriting ? WRITING_ACTION_MODE : DRAWING_ACTION_MODE) | Shell.ActionMode.NORMAL; + if (!Main.pushModal(this.activeArea, { actionMode: actionMode })) return false; this.addInternalKeybindings(); this.activeArea.reactive = true; @@ -389,6 +409,10 @@ var AreaManager = new Lang.Class({ this.indicator.sync(Boolean(this.activeArea)); }, + updateActionMode: function() { + Main.actionMode = (this.activeArea.isWriting ? WRITING_ACTION_MODE : DRAWING_ACTION_MODE) | Shell.ActionMode.NORMAL; + }, + // Use level -1 to set no level through a signal. showOsd: function(emitter, icon, label, color, level, long) { let activeIndex = this.areas.indexOf(this.activeArea); @@ -477,6 +501,7 @@ var AreaManager = new Lang.Class({ for (let i = 0; i < this.areas.length; i++) { let area = this.areas[i]; area.disconnect(area.leaveDrawingHandler); + area.disconnect(area.updateActionModeHandler); area.disconnect(area.showOsdHandler); let container = area.get_parent(); container.get_parent().remove_actor(container); From 8519948c3b8c758e7309dfabbc1488fe6211a50e Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 26 Jun 2020 12:19:53 +0200 Subject: [PATCH 51/67] Render texts with Pango Cairo "toy" text tool does not render non-latin and emoji characters. Moreover Pango provides a larger set of font attributes. Fixes #29, #34 --- data/default.css | 3 - draw.js | 124 +++++++++++++++++---------------- locale/draw-on-your-screen.pot | 27 +++++++ 3 files changed, 91 insertions(+), 63 deletions(-) diff --git a/data/default.css b/data/default.css index bae96a6..819219a 100644 --- a/data/default.css +++ b/data/default.css @@ -29,9 +29,6 @@ * Only one family : no comma separated list of families like "font1, font2, ..., Sans-Serif". * Font family can be any font installed, or a generic family name (Serif, Sans-Serif, Monospace, Cursive, Fantasy). * Font weight and font style : no upper case when string. - * Weight <= 500 (or lighter, normal, medium) is rendered as normal. - * Weight > 500 (or bolder, bold) is rendered as bold. - * Oblique and italic style supports depend on the font family and seem to be rendered identically. * */ diff --git a/draw.js b/draw.js index a4d79dd..ad076eb 100644 --- a/draw.js +++ b/draw.js @@ -28,7 +28,8 @@ const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; -const PangoMatrix = imports.gi.Pango.Matrix; +const Pango = imports.gi.Pango; +const PangoCairo = imports.gi.PangoCairo; const St = imports.gi.St; const BoxPointer = imports.ui.boxpointer; @@ -63,17 +64,25 @@ const FILLRULE_EVENODD_ICON_PATH = ICON_DIR.get_child('fillrule-evenodd-symbolic const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get_path(); const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path(); +const reverseEnumeration = function(obj) { + return Object.fromEntries(Object.entries(obj).map(entry => [entry[1], entry[0].slice(0,1) + entry[0].slice(1).toLowerCase()])); +}; + const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; var Tools = Object.assign({}, Shapes, Manipulations); const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 }; const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" }; -const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; -const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; +const LineCapNames = reverseEnumeration(Cairo.LineCap); +const LineJoinNames = reverseEnumeration(Cairo.LineJoin); const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; -const FontWeightNames = { 0: 'Normal', 1: 'Bold' }; -const FontStyleNames = { 0: 'Normal', 1: 'Italic', 2: 'Oblique' }; -const FontFamilyNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; +const FontGenericNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; +const FontStyleNames = reverseEnumeration(Pango.Style); +const FontWeightNames = Object.assign( + reverseEnumeration(Pango.Weight), + { 200: "Ultra-light", 350: "Semi-light", 600: "Semi-bold", 800: "Ultra-bold" } +); +delete FontWeightNames[Pango.Weight.ULTRAHEAVY]; const getDateString = function() { let date = GLib.DateTime.new_now_local(); @@ -136,7 +145,7 @@ var DrawingArea = new Lang.Class({ this.undoneElements = []; this.currentElement = null; this.currentTool = Shapes.NONE; - this.currentFontFamilyId = 0; + this.currentFontGeneric = 0; this.isSquareArea = false; this.hasGrid = false; this.hasBackground = false; @@ -191,7 +200,7 @@ var DrawingArea = new Lang.Class({ } let font = themeNode.get_font(); this.newThemeAttributes.ThemeFontFamily = font.get_family(); - this.newThemeAttributes.FontWeight = font.get_weight(); + try { this.newThemeAttributes.FontWeight = font.get_weight(); } catch(e) { this.newThemeAttributes.FontWeight = Pango.Weight.NORMAL; } this.newThemeAttributes.FontStyle = font.get_style(); this.newThemeAttributes.LineWidth = themeNode.get_length('-drawing-line-width'); this.newThemeAttributes.LineJoin = themeNode.get_double('-drawing-line-join'); @@ -214,13 +223,12 @@ var DrawingArea = new Lang.Class({ this.colors[i] = this.colors[i].alpha ? this.colors[i] : this.colors[0]; } this.currentColor = this.currentColor || this.colors[1]; + // SVG does not support 'Ultra-heavy' weight (1000) + this.newThemeAttributes.FontWeight = Math.min(this.newThemeAttributes.FontWeight, 900); this.newThemeAttributes.LineWidth = (this.newThemeAttributes.LineWidth > 0) ? this.newThemeAttributes.LineWidth : 3; this.newThemeAttributes.LineJoin = ([0, 1, 2].indexOf(this.newThemeAttributes.LineJoin) != -1) ? this.newThemeAttributes.LineJoin : Cairo.LineJoin.ROUND; this.newThemeAttributes.LineCap = ([0, 1, 2].indexOf(this.newThemeAttributes.LineCap) != -1) ? this.newThemeAttributes.LineCap : Cairo.LineCap.ROUND; this.newThemeAttributes.FillRule = ([0, 1].indexOf(this.newThemeAttributes.FillRule) != -1) ? this.newThemeAttributes.FillRule : Cairo.FillRule.WINDING; - this.newThemeAttributes.FontWeight = this.newThemeAttributes.FontWeight > 500 ? 1 : 0 ; - // font style enum order of Cairo and Pango are different - this.newThemeAttributes.FontStyle = this.newThemeAttributes.FontStyle == 2 ? 1 : ( this.newThemeAttributes.FontStyle == 1 ? 2 : 0); for (const attributeName in this.newThemeAttributes) { if (this.newThemeAttributes[attributeName] != this.oldThemeAttributes[attributeName]) { this.oldThemeAttributes[attributeName] = this.newThemeAttributes[attributeName]; @@ -559,13 +567,13 @@ var DrawingArea = new Lang.Class({ fillRule: this.currentFillRule, eraser: eraser, transform: { active: false, center: [0, 0], angle: 0, startAngle: 0, ratio: 1 }, - text: '', - font: { family: (this.currentFontFamilyId == 0 ? this.currentThemeFontFamily : FontFamilyNames[this.currentFontFamilyId]), weight: this.currentFontWeight, style: this.currentFontStyle }, points: [] }); if (this.currentTool == Shapes.TEXT) { this.currentElement.fill = false; + this.currentElement.font = { family: (this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric]), + weight: this.currentFontWeight, style: this.currentFontStyle }; this.currentElement.text = _("Text"); this.currentElement.rtl = ENABLE_RTL && this.get_text_direction() == Clutter.TextDirection.RTL; } @@ -869,17 +877,19 @@ var DrawingArea = new Lang.Class({ }, toggleFontWeight: function() { - this.currentFontWeight = this.currentFontWeight == 1 ? 0 : this.currentFontWeight + 1; - if (this.currentElement) { + let fontWeights = Object.keys(FontWeightNames).map(key => Number(key)); + let index = fontWeights.indexOf(this.currentFontWeight); + this.currentFontWeight = index == fontWeights.length - 1 ? fontWeights[0] : fontWeights[index + 1]; + if (this.currentElement && this.currentElement.font) { this.currentElement.font.weight = this.currentFontWeight; this._redisplay(); } - this.emit('show-osd', null, `${_(FontWeightNames[this.currentFontWeight])}`, "", -1); + this.emit('show-osd', null, `${_(FontWeightNames[this.currentFontWeight])}`, "", -1); }, toggleFontStyle: function() { this.currentFontStyle = this.currentFontStyle == 2 ? 0 : this.currentFontStyle + 1; - if (this.currentElement) { + if (this.currentElement && this.currentElement.font) { this.currentElement.font.style = this.currentFontStyle; this._redisplay(); } @@ -887,9 +897,9 @@ var DrawingArea = new Lang.Class({ }, toggleFontFamily: function() { - this.currentFontFamilyId = this.currentFontFamilyId == 5 ? 0 : this.currentFontFamilyId + 1; - let currentFontFamily = this.currentFontFamilyId == 0 ? this.currentThemeFontFamily : FontFamilyNames[this.currentFontFamilyId]; - if (this.currentElement) { + this.currentFontGeneric = this.currentFontGeneric == 5 ? 0 : this.currentFontGeneric + 1; + let currentFontFamily = this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric]; + if (this.currentElement && this.currentElement.font) { this.currentElement.font.family = currentFontFamily; this._redisplay(); } @@ -1187,8 +1197,14 @@ const DrawingElement = new Lang.Class({ this.fillRule = Cairo.FillRule.WINDING; if (params.transformations === undefined) this.transformations = []; - if (params.shape == Shapes.TEXT && params.rtl === undefined) - this.rtl = false; + if (params.shape == Shapes.TEXT) { + if (params.rtl === undefined) + this.rtl = false; + if (params.font && params.font.weight === 0) + this.font.weight = 400; + if (params.font && params.font.weight === 1) + this.font.weight = 700; + } if (params.transform && params.transform.center) { let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0); @@ -1328,44 +1344,32 @@ const DrawingElement = new Lang.Class({ cr.closePath(); } else if (shape == Shapes.TEXT && points.length == 2) { - cr.selectFontFace(this.font.family, this.font.style, this.font.weight); - cr.setFontSize(Math.abs(points[1][1] - points[0][1])); - - let textWidth = 0; - - if (this.rtl) { - cr.save(); - cr.setSourceRGBA(0, 0, 0, 0); - cr.setOperator(Cairo.Operator.OVER); - cr.moveTo(points[1][0], Math.max(points[0][1], points[1][1])); - cr.showText(this.text); - textWidth = cr.getCurrentPoint()[0] - points[1][0]; - cr.restore(); - } + let layout = PangoCairo.create_layout(cr); + let fontSize = Math.abs(points[1][1] - points[0][1]) * Pango.SCALE; + let fontDescription = new Pango.FontDescription(); + fontDescription.set_family(this.font.family); + fontDescription.set_style(this.font.style); + fontDescription.set_weight(this.font.weight); + fontDescription.set_absolute_size(fontSize); + layout.set_font_description(fontDescription); + layout.set_text(this.text, -1); + this.textWidth = layout.get_pixel_size()[0]; + cr.moveTo(points[1][0] - (this.rtl ? this.textWidth : 0), Math.max(points[0][1],points[1][1]) - layout.get_baseline() / Pango.SCALE); + layout.set_text(this.text, -1); + PangoCairo.show_layout(cr, layout); if (params.showTextCursor) { let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition; - let texts = [this.text.slice(0, cursorPosition), this.text.slice(cursorPosition)]; - cr.moveTo(points[1][0] - textWidth, Math.max(points[0][1], points[1][1])); - cr.showText(texts[0]); - let currentPoint1 = cr.getCurrentPoint(); - cr.rectangle(currentPoint1[0], currentPoint1[1], Math.abs(points[1][1] - points[0][1]) / 25, - Math.abs(points[1][1] - points[0][1])); + layout.set_text(this.text.slice(0, cursorPosition), -1); + let width = layout.get_pixel_size()[0]; + cr.rectangle(points[1][0] - (this.rtl ? this.textWidth : 0) + width, Math.max(points[0][1],points[1][1]), + Math.abs(points[1][1] - points[0][1]) / 25, - Math.abs(points[1][1] - points[0][1])); cr.fill(); - cr.moveTo(currentPoint1[0], currentPoint1[1]); - cr.showText(texts[1]); - textWidth = this.rtl ? textWidth : (cr.getCurrentPoint()[0] - points[1][0]); - } else { - cr.moveTo(points[1][0] - textWidth, Math.max(points[0][1], points[1][1])); - cr.showText(this.text); - textWidth = this.rtl ? textWidth : (cr.getCurrentPoint()[0] - points[1][0]); } - if (!params.showTextCursor) - this.textWidth = textWidth; - if (params.showTextRectangle || params.drawTextRectangle) { - cr.rectangle(points[1][0] - (this.rtl ? textWidth : 0), Math.max(points[0][1], points[1][1]), - textWidth, - Math.abs(points[1][1] - points[0][1])); + cr.rectangle(points[1][0] - (this.rtl ? this.textWidth : 0), Math.max(points[0][1], points[1][1]), + this.textWidth, - Math.abs(points[1][1] - points[0][1])); if (params.showTextRectangle) setDummyStroke(cr); else @@ -1490,7 +1494,7 @@ const DrawingElement = new Lang.Class({ `stroke-opacity="0" ` + `font-family="${this.font.family}" ` + `font-size="${Math.abs(points[1][1] - points[0][1])}" ` + - `font-weight="${FontWeightNames[this.font.weight].toLowerCase()}" ` + + `font-weight="${this.font.weight}" ` + `font-style="${FontStyleNames[this.font.style].toLowerCase()}"`; row += ` { for (let i in obj) { let text; - if (targetProperty == 'currentFontFamilyId') + if (targetProperty == 'currentFontGeneric') text = `${_(obj[i])}`; else if (targetProperty == 'currentFontWeight') - text = `${_(obj[i])}`; + text = `${_(obj[i])}`; else if (targetProperty == 'currentFontStyle') text = `${_(obj[i])}`; else diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 7a2bec5..fc3702a 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -436,12 +436,39 @@ msgstr "" #msgid "Evenodd" #msgstr "" +#msgid "Thin" +#msgstr "" + +#msgid "Ultra-light" +#msgstr "" + +#msgid "Light" +#msgstr "" + +#msgid "Semi-light" +#msgstr "" + +#msgid "Book" +#msgstr "" + #msgid "Normal" #msgstr "" +#msgid "Medium" +#msgstr "" + +#msgid "Semi-bold" +#msgstr "" + #msgid "Bold" #msgstr "" +#msgid "Ultra-bold" +#msgstr "" + +#msgid "Heavy" +#msgstr "" + #msgid "Italic" #msgstr "" From 119c6725ef8c2f1aa14f92d045f8bf5b30c89e2d Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 26 Jun 2020 19:01:56 +0200 Subject: [PATCH 52/67] Font stretch and variant Add minimal (no UI) `font-stretch` and `font-variant` support. `font-stretch` css attribute is not recognized. Cannot find a working usage for both. --- draw.js | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/draw.js b/draw.js index ad076eb..1e0e117 100644 --- a/draw.js +++ b/draw.js @@ -65,7 +65,9 @@ const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path(); const reverseEnumeration = function(obj) { - return Object.fromEntries(Object.entries(obj).map(entry => [entry[1], entry[0].slice(0,1) + entry[0].slice(1).toLowerCase()])); + return Object.fromEntries(Object.entries(obj).map(entry => + [entry[1], entry[0].slice(0,1) + entry[0].slice(1).toLowerCase().replace('_', '-')] + )); }; const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; @@ -77,12 +79,14 @@ const LineCapNames = reverseEnumeration(Cairo.LineCap); const LineJoinNames = reverseEnumeration(Cairo.LineJoin); const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; const FontGenericNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; -const FontStyleNames = reverseEnumeration(Pango.Style); const FontWeightNames = Object.assign( reverseEnumeration(Pango.Weight), { 200: "Ultra-light", 350: "Semi-light", 600: "Semi-bold", 800: "Ultra-bold" } ); delete FontWeightNames[Pango.Weight.ULTRAHEAVY]; +const FontStyleNames = reverseEnumeration(Pango.Style); +const FontStretchNames = reverseEnumeration(Pango.Stretch); +const FontVariantNames = reverseEnumeration(Pango.Variant); const getDateString = function() { let date = GLib.DateTime.new_now_local(); @@ -202,6 +206,8 @@ var DrawingArea = new Lang.Class({ this.newThemeAttributes.ThemeFontFamily = font.get_family(); try { this.newThemeAttributes.FontWeight = font.get_weight(); } catch(e) { this.newThemeAttributes.FontWeight = Pango.Weight.NORMAL; } this.newThemeAttributes.FontStyle = font.get_style(); + this.newThemeAttributes.FontStretch = font.get_stretch(); + this.newThemeAttributes.FontVariant = font.get_variant(); this.newThemeAttributes.LineWidth = themeNode.get_length('-drawing-line-width'); this.newThemeAttributes.LineJoin = themeNode.get_double('-drawing-line-join'); this.newThemeAttributes.LineCap = themeNode.get_double('-drawing-line-cap'); @@ -572,8 +578,12 @@ var DrawingArea = new Lang.Class({ if (this.currentTool == Shapes.TEXT) { this.currentElement.fill = false; - this.currentElement.font = { family: (this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric]), - weight: this.currentFontWeight, style: this.currentFontStyle }; + this.currentElement.font = { + family: (this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric]), + weight: this.currentFontWeight, + style: this.currentFontStyle, + stretch: this.currentFontStretch, + variant: this.currentFontVariant }; this.currentElement.text = _("Text"); this.currentElement.rtl = ENABLE_RTL && this.get_text_direction() == Clutter.TextDirection.RTL; } @@ -1347,10 +1357,13 @@ const DrawingElement = new Lang.Class({ let layout = PangoCairo.create_layout(cr); let fontSize = Math.abs(points[1][1] - points[0][1]) * Pango.SCALE; let fontDescription = new Pango.FontDescription(); - fontDescription.set_family(this.font.family); - fontDescription.set_style(this.font.style); - fontDescription.set_weight(this.font.weight); fontDescription.set_absolute_size(fontSize); + ['family', 'weight', 'style', 'stretch', 'variant'].forEach(attribute => { + if (this.font[attribute] !== undefined) + try { + fontDescription[`set_${attribute}`](this.font[attribute]); + } catch(e) {} + }); layout.set_font_description(fontDescription); layout.set_text(this.text, -1); this.textWidth = layout.get_pixel_size()[0]; @@ -1492,10 +1505,18 @@ const DrawingElement = new Lang.Class({ attributes = `fill="${color}" ` + `stroke="transparent" ` + `stroke-opacity="0" ` + - `font-family="${this.font.family}" ` + - `font-size="${Math.abs(points[1][1] - points[0][1])}" ` + - `font-weight="${this.font.weight}" ` + - `font-style="${FontStyleNames[this.font.style].toLowerCase()}"`; + `font-size="${Math.abs(points[1][1] - points[0][1])}"`; + + if (this.font.family) + attributes += ` font-family="${this.font.family}"`; + if (this.font.weight && this.font.weight != Pango.Weight.NORMAL) + attributes += ` font-weight="${this.font.weight}"`; + if (this.font.style && FontStyleNames[this.font.style]) + attributes += ` font-style="${FontStyleNames[this.font.style].toLowerCase()}"`; + if (FontStretchNames[this.font.stretch] && this.font.stretch != Pango.Stretch.NORMAL) + attributes += ` font-stretch="${FontStretchNames[this.font.stretch].toLowerCase()}"`; + if (this.font.variant && FontVariantNames[this.font.variant]) + attributes += ` font-variant="${FontVariantNames[this.font.variant].toLowerCase()}"`; row += `${this.text}`; From e341b36d650708291bb5d39d2e26bf870b2d480b Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 26 Jun 2020 20:08:36 +0200 Subject: [PATCH 53/67] cr.$dispose on painting error Put repaint process in a "try catch" so cr.$dispose is always called. --- draw.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/draw.js b/draw.js index 1e0e117..5490b81 100644 --- a/draw.js +++ b/draw.js @@ -191,6 +191,18 @@ var DrawingArea = new Lang.Class({ this._stopElementGrabber(); }, + vfunc_repaint: function() { + let cr = this.get_context(); + + try { + this._repaint(cr); + } catch(e) { + logError(e, "An error occured while painting"); + } + + cr.$dispose(); + }, + _redisplay: function() { // force area to emit 'repaint' this.queue_repaint(); @@ -247,8 +259,7 @@ var DrawingArea = new Lang.Class({ this.gridColor = this.gridColor && this.gridColor.alpha ? this.gridColor : Clutter.Color.new(127, 127, 127, 255); }, - vfunc_repaint: function() { - let cr = this.get_context(); + _repaint: function(cr) { if (CAIRO_DEBUG_EXTENDS) { cr.scale(0.5, 0.5); cr.translate(this.monitor.width, this.monitor.height); @@ -304,8 +315,6 @@ var DrawingArea = new Lang.Class({ } cr.restore(); } - - cr.$dispose(); }, _onButtonPressed: function(actor, event) { From 8fae815ed89b1fa9a5f24e96a76824059d493325 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 11:42:53 +0200 Subject: [PATCH 54/67] Change color and tool icons --- data/icons/color-symbolic.svg | 21 +++++++++++++++++++++ draw.js | 6 ++++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 data/icons/color-symbolic.svg diff --git a/data/icons/color-symbolic.svg b/data/icons/color-symbolic.svg new file mode 100644 index 0000000..bd2b14a --- /dev/null +++ b/data/icons/color-symbolic.svg @@ -0,0 +1,21 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 +https://svgsilh.com/image/1745699.html +https://creativecommons.org/publicdomain/zero/1.0 + + + + + diff --git a/draw.js b/draw.js index 5490b81..c9f27bb 100644 --- a/draw.js +++ b/draw.js @@ -55,6 +55,7 @@ const TEXT_CURSOR_TIME = 600; // ms const ENABLE_RTL = false; const ICON_DIR = Me.dir.get_child('data').get_child('icons'); +const COLOR_ICON_PATH = ICON_DIR.get_child('color-symbolic.svg').get_path(); const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path(); const STROKE_ICON_PATH = ICON_DIR.get_child('stroke-symbolic.svg').get_path(); const LINEJOIN_ICON_PATH = ICON_DIR.get_child('linejoin-symbolic.svg').get_path(); @@ -2066,6 +2067,7 @@ const DrawingMenu = new Lang.Class({ menuCloseFunc.bind(this.menu)(animate); }; + this.colorIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(COLOR_ICON_PATH) }); this.strokeIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(STROKE_ICON_PATH) }); this.fillIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILL_ICON_PATH) }); this.fillRuleNonzeroIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILLRULE_NONZERO_ICON_PATH) }); @@ -2129,7 +2131,7 @@ const DrawingMenu = new Lang.Class({ this.menu.addAction(_("Smooth"), this.area.smoothLastElement.bind(this.area), 'format-text-strikethrough-symbolic'); this._addSeparator(this.menu); - this._addSubMenuItem(this.menu, null, ToolNames, this.area, 'currentTool', this._updateSectionVisibility.bind(this)); + this._addSubMenuItem(this.menu, 'document-edit-symbolic', ToolNames, this.area, 'currentTool', this._updateSectionVisibility.bind(this)); this._addColorSubMenuItem(this.menu); this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill', this._updateSectionVisibility.bind(this)); this.fillSection = new PopupMenu.PopupMenuSection(); @@ -2302,7 +2304,7 @@ const DrawingMenu = new Lang.Class({ _addColorSubMenuItem: function(menu) { let item = new PopupMenu.PopupSubMenuMenuItem(_("Color"), true); - item.icon.set_icon_name('document-edit-symbolic'); + item.icon.set_gicon(this.colorIcon); item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`); item.menu.itemActivated = () => { From c5f604a80aa656c1ebe54d617999b0c83b6747ff Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 13:40:34 +0200 Subject: [PATCH 55/67] Evenodd switch and svg attributes * Menu: `fillRule` submenu -> `Evenodd` switch. * Rework SVG attributes in order to bypass useless attributes. * Fix gjs bug: `Cairo.LineCap.SQUASH` -> `SQUARE`. * Not to draw dashes if dash.array is `[0, 0]` (case line width is 0). --- draw.js | 66 +++++++++++++++++++--------------- locale/draw-on-your-screen.pot | 5 +++ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/draw.js b/draw.js index c9f27bb..d75088f 100644 --- a/draw.js +++ b/draw.js @@ -76,14 +76,11 @@ const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; var Tools = Object.assign({}, Shapes, Manipulations); const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 }; const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" }; -const LineCapNames = reverseEnumeration(Cairo.LineCap); +const LineCapNames = Object.assign(reverseEnumeration(Cairo.LineCap), { 2: 'Square' }); const LineJoinNames = reverseEnumeration(Cairo.LineJoin); const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' }; const FontGenericNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; -const FontWeightNames = Object.assign( - reverseEnumeration(Pango.Weight), - { 200: "Ultra-light", 350: "Semi-light", 600: "Semi-bold", 800: "Ultra-bold" } -); +const FontWeightNames = Object.assign(reverseEnumeration(Pango.Weight), { 200: "Ultra-light", 350: "Semi-light", 600: "Semi-bold", 800: "Ultra-bold" }); delete FontWeightNames[Pango.Weight.ULTRAHEAVY]; const FontStyleNames = reverseEnumeration(Pango.Style); const FontStretchNames = reverseEnumeration(Pango.Stretch); @@ -192,6 +189,15 @@ var DrawingArea = new Lang.Class({ this._stopElementGrabber(); }, + // Boolean wrapper for switch menu item. + get currentEvenodd() { + return this.currentFillRule == Cairo.FillRule.EVEN_ODD; + }, + + set currentEvenodd(evenodd) { + this.currentFillRule = evenodd ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING; + }, + vfunc_repaint: function() { let cr = this.get_context(); @@ -1278,9 +1284,10 @@ const DrawingElement = new Lang.Class({ cr.setLineCap(this.line.lineCap); cr.setLineJoin(this.line.lineJoin); cr.setLineWidth(this.line.lineWidth); - cr.setFillRule(this.fillRule); + if (this.fillRule) + cr.setFillRule(this.fillRule); - if (this.dash.active) + if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1]) cr.setDash(this.dash.array, this.dash.offset); if (this.eraser) @@ -1423,22 +1430,28 @@ const DrawingElement = new Lang.Class({ 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; let fill = this.fill && !this.isStraightLine; - let attributes; + let attributes = ''; - if (this.isStraightLine) - attributes = `stroke="${color}" ` + - `stroke-width="${this.line.lineWidth}" ` + - `stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`; - else - attributes = `fill="${fill ? color : 'transparent'}" ` + - (fill ? `fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}" ` : `fill-opacity="0" `) + - `stroke="${color}" ` + - `stroke-width="${this.line.lineWidth}" ` + - `stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}" ` + - `stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`; - - if (this.dash.active) - attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; + if (fill) { + attributes = `fill="${color}"`; + if (this.fillRule) + attributes += ` fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}"`; + } else { + attributes = `fill="none"`; + } + + if (this.line && this.line.lineWidth) { + attributes += ` stroke="${color}"` + + ` stroke-width="${this.line.lineWidth}"`; + if (this.line.lineCap) + attributes += ` stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`; + if (this.line.lineJoin && !this.isStraightLine) + attributes += ` stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`; + if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1]) + attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; + } else { + attributes += ` stroke="none"`; + } let transAttribute = ''; this.transformations.slice(0).reverse().forEach(transformation => { @@ -2135,7 +2148,8 @@ const DrawingMenu = new Lang.Class({ this._addColorSubMenuItem(this.menu); this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill', this._updateSectionVisibility.bind(this)); this.fillSection = new PopupMenu.PopupMenuSection(); - this._addSubMenuItem(this.fillSection, this.fillRuleNonzeroIcon, FillRuleNames, this.area, 'currentFillRule', this._updateFillRuleIcon.bind(this)); + this.fillSection.itemActivated = () => {}; + this.fillRuleItem = this._addSwitchItem(this.fillSection, _("Evenodd"), this.fillRuleNonzeroIcon, this.fillRuleEvenoddIcon, this.area, 'currentEvenodd'); this.menu.addMenuItem(this.fillSection); this._addSeparator(this.menu); @@ -2175,7 +2189,6 @@ const DrawingMenu = new Lang.Class({ this.menu.addAction(_("Show help"), () => { this.close(); this.area.toggleHelp(); }, 'preferences-desktop-keyboard-shortcuts-symbolic'); this._updateSectionVisibility(); - this._updateFillRuleIcon(); }, _updateSectionVisibility: function() { @@ -2197,11 +2210,6 @@ const DrawingMenu = new Lang.Class({ this.fillSection.actor.hide(); }, - _updateFillRuleIcon: function() { - let fillRuleIcon = this.area.currentFillRule == Cairo.FillRule.EVEN_ODD ? this.fillRuleEvenoddIcon : this.fillRuleNonzeroIcon; - this.fillSection.firstMenuItem.icon.set_gicon(fillRuleIcon); - }, - _addSwitchItem: function(menu, label, iconFalse, iconTrue, target, targetProperty, onToggled) { let item = new PopupMenu.PopupSwitchMenuItem(label, target[targetProperty]); diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index fc3702a..3fdf137 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -135,6 +135,10 @@ msgstr "" msgid "Smooth" msgstr "" +# Evenodd is a fill rule SVG attribute (even as 2,4,6,... and odd as 1,3,5,...) +msgid "Evenodd" +msgstr "" + msgid "Dashed" msgstr "" @@ -433,6 +437,7 @@ msgstr "" #msgid "Nonzero" #msgstr "" +#already in menu #msgid "Evenodd" #msgstr "" From dc73000a1dc572caf3ec5237a5ee1c94beea0c8b Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 14:00:21 +0200 Subject: [PATCH 56/67] Minor, reorder pot file --- locale/draw-on-your-screen.pot | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 3fdf137..18fa325 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -74,21 +74,6 @@ msgstr "" msgid "Mirror" msgstr "" -msgid "Fill" -msgstr "" - -msgid "Stroke" -msgstr "" - -msgid "Dashed line" -msgstr "" - -msgid "Full line" -msgstr "" - -msgid "%d px" -msgstr "" - msgid "Press %s to get a fourth control point" msgstr "" @@ -108,6 +93,23 @@ msgid "" "Type your text and press %s" msgstr "" +msgid "Fill" +msgstr "" + +msgid "Stroke" +msgstr "" + +msgid "Dashed line" +msgstr "" + +msgid "Full line" +msgstr "" + +msgid "%d px" +msgstr "" + +#: helper + msgid "Screenshot" msgstr "" @@ -123,6 +125,8 @@ msgstr "" msgid "System" msgstr "" +#: menu + msgid "Undo" msgstr "" From fbe044a2b98f22e81af71db7b66a6a211cfc8c76 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 15:10:01 +0200 Subject: [PATCH 57/67] Add text alignment Left or right. Replace unused `rtl`. --- data/default.css | 3 ++ draw.js | 41 +++++++++++++----- extension.js | 1 + locale/draw-on-your-screen.pot | 11 ++++- prefs.js | 3 +- schemas/gschemas.compiled | Bin 4192 -> 4280 bytes ...extensions.draw-on-your-screen.gschema.xml | 5 +++ 7 files changed, 51 insertions(+), 13 deletions(-) diff --git a/data/default.css b/data/default.css index 819219a..4a03e08 100644 --- a/data/default.css +++ b/data/default.css @@ -30,6 +30,8 @@ * Font family can be any font installed, or a generic family name (Serif, Sans-Serif, Monospace, Cursive, Fantasy). * Font weight and font style : no upper case when string. * + * text-align: left or right. + * */ .draw-on-your-screen { @@ -50,6 +52,7 @@ font-family: Cantarell; font-weight: normal; font-style: normal; + text-align: left; } /* Palette */ diff --git a/draw.js b/draw.js index d75088f..a926fd6 100644 --- a/draw.js +++ b/draw.js @@ -52,7 +52,6 @@ const CAIRO_DEBUG_EXTENDS = false; const SVG_DEBUG_EXTENDS = false; const SVG_DEBUG_SUPERPOSES_CAIRO = false; const TEXT_CURSOR_TIME = 600; // ms -const ENABLE_RTL = false; const ICON_DIR = Me.dir.get_child('data').get_child('icons'); const COLOR_ICON_PATH = ICON_DIR.get_child('color-symbolic.svg').get_path(); @@ -227,6 +226,7 @@ var DrawingArea = new Lang.Class({ this.newThemeAttributes.FontStyle = font.get_style(); this.newThemeAttributes.FontStretch = font.get_stretch(); this.newThemeAttributes.FontVariant = font.get_variant(); + this.newThemeAttributes.TextRightAligned = themeNode.get_text_align() == St.TextAlign.RIGHT; this.newThemeAttributes.LineWidth = themeNode.get_length('-drawing-line-width'); this.newThemeAttributes.LineJoin = themeNode.get_double('-drawing-line-join'); this.newThemeAttributes.LineCap = themeNode.get_double('-drawing-line-cap'); @@ -601,7 +601,7 @@ var DrawingArea = new Lang.Class({ stretch: this.currentFontStretch, variant: this.currentFontVariant }; this.currentElement.text = _("Text"); - this.currentElement.rtl = ENABLE_RTL && this.get_text_direction() == Clutter.TextDirection.RTL; + this.currentElement.textRightAligned = this.currentTextRightAligned; } this.currentElement.startDrawing(startX, startY); @@ -932,6 +932,15 @@ var DrawingArea = new Lang.Class({ this.emit('show-osd', null, `${_(currentFontFamily)}`, "", -1); }, + toggleTextAlignment: function() { + this.currentTextRightAligned = !this.currentTextRightAligned; + if (this.currentElement && this.currentElement.textRightAligned !== undefined) { + this.currentElement.textRightAligned = this.currentTextRightAligned; + this._redisplay(); + } + this.emit('show-osd', null, this.currentTextRightAligned ? _("Right aligned") : _("Left aligned"), "", -1); + }, + toggleHelp: function() { if (this.helper.visible) { this.helper.hideHelp(); @@ -1224,8 +1233,6 @@ const DrawingElement = new Lang.Class({ if (params.transformations === undefined) this.transformations = []; if (params.shape == Shapes.TEXT) { - if (params.rtl === undefined) - this.rtl = false; if (params.font && params.font.weight === 0) this.font.weight = 400; if (params.font && params.font.weight === 1) @@ -1258,7 +1265,7 @@ const DrawingElement = new Lang.Class({ transformations: this.transformations, text: this.text, lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined, - rtl: this.rtl, + textRightAligned: this.textRightAligned, font: this.font, points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) }; @@ -1384,7 +1391,7 @@ const DrawingElement = new Lang.Class({ layout.set_font_description(fontDescription); layout.set_text(this.text, -1); this.textWidth = layout.get_pixel_size()[0]; - cr.moveTo(points[1][0] - (this.rtl ? this.textWidth : 0), Math.max(points[0][1],points[1][1]) - layout.get_baseline() / Pango.SCALE); + cr.moveTo(points[1][0] - (this.textRightAligned ? this.textWidth : 0), Math.max(points[0][1],points[1][1]) - layout.get_baseline() / Pango.SCALE); layout.set_text(this.text, -1); PangoCairo.show_layout(cr, layout); @@ -1392,13 +1399,13 @@ const DrawingElement = new Lang.Class({ let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition; layout.set_text(this.text.slice(0, cursorPosition), -1); let width = layout.get_pixel_size()[0]; - cr.rectangle(points[1][0] - (this.rtl ? this.textWidth : 0) + width, Math.max(points[0][1],points[1][1]), + cr.rectangle(points[1][0] - (this.textRightAligned ? this.textWidth : 0) + width, Math.max(points[0][1],points[1][1]), Math.abs(points[1][1] - points[0][1]) / 25, - Math.abs(points[1][1] - points[0][1])); cr.fill(); } if (params.showTextRectangle || params.drawTextRectangle) { - cr.rectangle(points[1][0] - (this.rtl ? this.textWidth : 0), Math.max(points[0][1], points[1][1]), + cr.rectangle(points[1][0] - (this.textRightAligned ? this.textWidth : 0), Math.max(points[0][1], points[1][1]), this.textWidth, - Math.abs(points[1][1] - points[0][1])); if (params.showTextRectangle) setDummyStroke(cr); @@ -1541,7 +1548,8 @@ const DrawingElement = new Lang.Class({ if (this.font.variant && FontVariantNames[this.font.variant]) attributes += ` font-variant="${FontVariantNames[this.font.variant].toLowerCase()}"`; - row += `${this.text}`; } @@ -1747,6 +1755,7 @@ const DrawingElement = new Lang.Class({ }, // The figure rotation center before transformations (original). + // this.textWidth is computed during Cairo building. _getOriginalCenter: function() { if (!this._originalCenter) { let points = this.points; @@ -2169,8 +2178,10 @@ const DrawingMenu = new Lang.Class({ this._addSubMenuItem(fontSection, 'font-x-generic-symbolic', FontGenericNamesCopy, this.area, 'currentFontGeneric'); this._addSubMenuItem(fontSection, 'format-text-bold-symbolic', FontWeightNames, this.area, 'currentFontWeight'); this._addSubMenuItem(fontSection, 'format-text-italic-symbolic', FontStyleNames, this.area, 'currentFontStyle'); + this._addSwitchItem(fontSection, _("Right aligned"), 'format-justify-left-symbolic', 'format-justify-right-symbolic', this.area, 'currentTextRightAligned'); this._addSeparator(fontSection); this.menu.addMenuItem(fontSection); + fontSection.itemActivated = () => {}; this.fontSection = fontSection; let manager = Extension.manager; @@ -2215,11 +2226,19 @@ const DrawingMenu = new Lang.Class({ item.icon = new St.Icon({ style_class: 'popup-menu-icon' }); getActor(item).insert_child_at_index(item.icon, 1); - item.icon.set_gicon(target[targetProperty] ? iconTrue : iconFalse); + let icon = target[targetProperty] ? iconTrue : iconFalse; + if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon)) + item.icon.set_gicon(icon); + else if (icon) + item.icon.set_icon_name(icon); item.connect('toggled', (item, state) => { target[targetProperty] = state; - item.icon.set_gicon(target[targetProperty] ? iconTrue : iconFalse); + let icon = target[targetProperty] ? iconTrue : iconFalse; + if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon)) + item.icon.set_gicon(icon); + else if (icon) + item.icon.set_icon_name(icon); if (onToggled) onToggled(); }); diff --git a/extension.js b/extension.js index 26cefb5..96e3551 100644 --- a/extension.js +++ b/extension.js @@ -214,6 +214,7 @@ var AreaManager = new Lang.Class({ 'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea), 'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea), 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), + 'toggle-text-alignment': this.activeArea.toggleTextAlignment.bind(this.activeArea), 'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this), 'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea), 'open-user-stylesheet': this.openUserStyleFile.bind(this), diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 18fa325..38b4edf 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -108,6 +108,12 @@ msgstr "" msgid "%d px" msgstr "" +msgid "Right aligned" +msgstr "" + +msgid "Left aligned" +msgstr "" + #: helper msgid "Screenshot" @@ -239,7 +245,7 @@ msgstr "" msgid "Change linecap" msgstr "" -msgid "Change fill rule" +msgid "Toggle fill rule" msgstr "" #: already in draw.js @@ -255,6 +261,9 @@ msgstr "" msgid "Change font style" msgstr "" +msgid "Toggle text alignment" +msgstr "" + msgid "Hide panel and dock" msgstr "" diff --git a/prefs.js b/prefs.js index 1f6cc25..3ea6e30 100644 --- a/prefs.js +++ b/prefs.js @@ -59,7 +59,7 @@ var INTERNAL_KEYBINDINGS = { 'select-mirror-tool': "Select mirror", '-separator-2': '', 'toggle-fill': "Toggle fill/stroke", - 'toggle-fill-rule': "Change fill rule", + 'toggle-fill-rule': "Toggle fill rule", '-separator-3': '', 'increment-line-width': "Increment line width", 'decrement-line-width': "Decrement line width", @@ -72,6 +72,7 @@ var INTERNAL_KEYBINDINGS = { 'toggle-font-family': "Change font family (generic name)", 'toggle-font-weight': "Change font weight", 'toggle-font-style': "Change font style", + 'toggle-text-alignment': "Toggle text alignment", '-separator-5': '', 'toggle-panel-and-dock-visibility': "Hide panel and dock", 'toggle-background': "Add a drawing background", diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index fda9879c7f963d5e881c849b17159b30b433f063..4ce499444b20d3b7b8ae1ad8e742897db4aa73c6 100644 GIT binary patch literal 4280 zcmai1ZHN_B7@jRHzxQgXt8S&Hc{iK8yXyLdZflvE73{JYOH?|0@7a5gJ9lQ9nY&+_ zNCkbEAW^hbk`<&z;6^4YhW4Y7An-?&5s`vH1<3}1LXteOw8RBtw%ct{t1}d+}}hy_3f}<2T#Nx!~9Y+ zzf-_hfIVAluh5@5@CUD*plRp*eV@`!Jp=8uf+di;!p`Hf&dGOD`1ATv7pg;9g*oVO>OvHoAPEGrKa0{?e*{Nx71v|jUU+=GB zJJjs=CGc^ew0_$$+Nr0*ejfY-@Q`YUn(h1wo`A{U^TNE8kF z173XZ-i!37o)7yV_$aVh`BT&XB={n5Xvc{z`cpHWVQ|%CP1~aUsp+2rHvkVSJ2mYs z;2pqLWv8ay0UrR&w(a+`eQL%(1pWp%cx<6fJN0DPFM@vuo<949Njo+3#>GD!c=Jr# z5be~Q-!ynVaOka9-lm;81$!%a5YXm+{}1idoZlnhGr+lL_e|z~rXCObdGOyr-EZ|1 z=ugc&q^4s30CS%x$4SjN8^9ZY&a`NxKQ-532ly%AwHuc|qn(=m1@K~{-z2XO4MgS%;`9tV2?ycejxwrf!kCp6<31Rn)b*6u#qskyIDf(mp^tHRr1aTnDVY|D(0EQ`6o8-UeJf zo_UUTYObpScsFqP^gk)usTt=1@EgFnE;);KYTA#0PXWW4CH+y=~>^KOt&YUZ;8EP>i>Hs`e2TH5gH zY)4pn$rC6ue!nSDi9}fFqUUTZ2l}Ed))-6!*1e|P1!Mqhv!IUDgK@-aI>0)_`ow1z z0!sj%^YZ~5hf!U^(!<k{i1>kDgG1Hd}Q`ql_61^7dnG}fMbfF>X(P-Kh3@^w>MLhqG1zYvZ8k=E_)rB2^9 zvVNS*qO?k0e7UZ@AiMnLvUTR7wQ+JCSM*4`n-iw+ zg>D+2uR}5%`fm)(NG*LlTx5OSvCaNGUJ$&3;Rrh67scDJ<|~+63$GRBuA}=wzuO!n zJ<(#;Hf@(Firc}s8{?}+ZQ7hs4{&~>cHzG)zFIbxKj$UszdXKL9{Njb^jwULYeOy< zKdEx>n3#~%;$)0I2Os7r>epBKQMn98j>Qn&$2eBRS1ZD}##$rTW$vi_t9X*jrZyQ> z&|y>D0~`}qO<{j(A3KI6Ox>_@dd|*v>pjwwozj$ke-v+FjPYQzMfcJE%J^z!*uEOR zWn;mIIjYFBGe*1Ee93lK#aF9Fv>S|)?{i82)$!HpvHaJJ@Lv=AuL=FfS|9&24_q$g z^G+odFWgQP*QQ3CC5f$omvQNMQBop55$2<4+kRoxK8^jJDdFU;4w_W`bxG6I-IB?* z68T|X9xIu?bj<$Ph|Mot>2=2zwVa|GGncd^o94HIwDM@T;&`7H_+%WzFK|-vtH%dN zeel6iAAE4s#~&Q^V|;MnIQPQnZ5)h2ePBCL{8HsZdF5T1tGJ(6_f>@apveiZ+qWIM z;g{~L^Nb#$8=mg<BAK|TAG2~?^x0`kE4sf^ z-B)cCZA?|%eH?Ew?4R!vD0f}L6;@VwLGBZAX6<6pQ2Ro-26kA*G$q%sz*`2hDXpB$ z8oupT3=HE6=aVb5SIB%JQJQ;4k1K09alG9l#wA@B9YiCG#-FWhr;B}c8AXZhS}|W; zm3|(!zl%lf3Zv4`Qhr!robafhfiNDm7u1VFbl(f_m*>*O3xHwq6|cM#B0pF81-)w8 Ef6s{ZTL1t6 literal 4192 zcmZ`+YlszP82&6b@9UbadD&_+%cC`Cchyx_biJ0F6|5|VMWnN5zCCB$IrE)n=IqT2 zQGu2Q7DjfFj3AdV47!jKxcw+32=+&m)E|X}gpz&)Eku{+ne)wMFCBPzo_Xgz@AuC8 zUFMzbkDH!tx`EKY`QY6Zow-TdCV?kk8E7W+URJb;Bj6La2vJol#J~7GQ3Hc`R6GUV z3n(w2&AX~7vwlH3PFD5>()DfS`dP~}dky6p{i^I4e$JE9<$pzHO-Mp;+?#Z4u&3v8(zXHr2 zSl2{5HRBJ0KLl#r1AVkp&xCyl{1veK4fbL1Rbc4gp&8s$)K#$m z0$zj-uxP*x$419!09(%dW&(WxlY=^$APM*_ovfNJrDLl@Oj|%FLr(ujtkB8 zFbuv1ENty>rk$GkaKp_3<~MHGN;@^{oB_80v;W+`gm!A$+rcKVuW@@AhnjULfe!;> z;SYb)pPKfQ;IqUxFVCl)n&S?Ee+3pkxj38`>Tvylr_IE??5O*h{?zoZ2X_Kn`hFXx zoq86>1s?~ZGm4sL6xqT-GSp?yqdztC?*qRGY}Ik78RsPUbKumuNf3V zH2i{DlJHsI?%74t>u)Wg^cmA%7N~sQkw%wt1Eb3<+D<>bQhr_9PX{Zi9%%$hIiXj| zuc-ZO*RpeFpghB}eY4Y%miEkaF7r+N+4K#+Cm-jrzN27w1+6|ZXF3*g4y56jeqcZr zrGBMu3+-acxSai<-@${$@ob$(NqWBR<7F8bd{NqNKFLG**4XS&J>*v@m}4gei{m<@ z>t&gKfdS+D7|&J~)=#Ra{xfCM!xMYbjQ!lGp1KzMeC4L&_@l=uD$8W8;$dr zJIWZZ)xv8xEL!CO*$i0H?+#Q6|G@Kj(o#_TCI7F~dqCG)KXOSq{prU}{L7>B>9U=0 z>f?B(zHdo%Ae=5EjMHmdLBS|0kBe@C@gHg5(^2jWJTn)dFG$s_WV+HZOcyhxa@|Ic z?c1HUV+WOGndDU%ofikqnWf}@_xSt!lpWzCDmSSz^CF?H7*iqS{8dW8(YmW?t^uRl>oV zT13f?AH7|wLnqrEzf|LRsQYHi8du}TVXGKH+UJqZvyt+SztB5 zz8tL@4p;V7>M7Iv|5sbEsn3}OfR3xxvZxw;Hn+F|AGnf zMPcK32(&V}S{dalJ=2$|H(GjqOUyJgPOlWs6MyNrtCFi#QQUBQl|0!cJ?ZA8AN%K2 zv1sc3pAO6qu`zL*leo=MT>Te(y4ocq9v!cVr<|Y;Sd{VjsKe^yYIPJ>&s(RN>&|-$ zM{FgX#ItjPxcD$m$6b?Lt%>5MA93xN=Ubcjubs%hWsHAI;@=YaM|JM8RoSl`Wnt!V SUg3GRP4&81`kYMtT>k;n5a!DO diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml index 38c9300..e9cb2bf 100644 --- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml +++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml @@ -231,6 +231,11 @@ toggle font style toggle font style + + ["<Primary><Shift>a"] + toggle text alignment + toggle text alignment + ["<Primary>o"] open user stylesheet to edit style From 06e931ffd2e1738771645d3ca64ab256d8ddf34f Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 16:23:28 +0200 Subject: [PATCH 58/67] Fix color icon Reduce svg size to avoid strange behaviour on menu opened. --- data/icons/color-symbolic.svg | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/data/icons/color-symbolic.svg b/data/icons/color-symbolic.svg index bd2b14a..39ae95d 100644 --- a/data/icons/color-symbolic.svg +++ b/data/icons/color-symbolic.svg @@ -1,21 +1,18 @@ - + Created by potrace 1.15, written by Peter Selinger 2001-2017 https://svgsilh.com/image/1745699.html https://creativecommons.org/publicdomain/zero/1.0 - - + + From 6a5481ff054e951caa315d6185c96ab9cc53e52f Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 16:55:26 +0200 Subject: [PATCH 59/67] Clean prefs and fix pot file --- locale/draw-on-your-screen.pot | 18 +++++------------- prefs.js | 20 ++------------------ 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 38b4edf..4b6a44e 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -338,10 +338,10 @@ msgstr "" msgid "Leave" msgstr "" -msgid "Select eraser (when starting a drawing)" +msgid "Select eraser (while starting drawing)" msgstr "" -msgid "Duplicate (when starting a transformation)" +msgid "Duplicate (while starting moving, resizing or mirroring)" msgstr "" msgid "Rotate rectangle, polygon, polyline" @@ -359,13 +359,13 @@ msgstr "" msgid "Smooth free drawing stroke" msgstr "" -msgid "Rotate (when moving)" +msgid "Rotate (while moving)" msgstr "" -msgid "Stretch (when resizing)" +msgid "Stretch (while resizing)" msgstr "" -msgid "Inverse (when mirroring)" +msgid "Inverse (while mirroring)" msgstr "" #: About page @@ -409,14 +409,6 @@ msgstr "" msgid "(in drawing mode)" msgstr "" -msgid "" -"By pressing Ctrl key during 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)\n" -" . smooth a free drawing stroke (you may prefer to smooth the stroke afterward, see “%s”)" -msgstr "" - msgid "" "Default drawing style attributes (color palette, font, line, dash) are defined in an editable css file.\n" "See “%s”." diff --git a/prefs.js b/prefs.js index 3ea6e30..d4a7837 100644 --- a/prefs.js +++ b/prefs.js @@ -105,8 +105,8 @@ var OTHER_SHORTCUTS = [ { desc: "Ignore pointer movement", get shortcut() { return _("%s held").format(getKeyLabel('space')); } }, { desc: "Leave", shortcut: getKeyLabel('Escape') }, { desc: "-separator-1", shortcut: "" }, - { desc: "Select eraser (while starting a drawing)", shortcut: "%s".format(getKeyLabel('')) }, - { desc: "Duplicate (while starting a transformation)", shortcut: "%s".format(getKeyLabel('')) }, + { desc: "Select eraser (while starting drawing)", shortcut: getKeyLabel('') }, + { desc: "Duplicate (while starting moving, resizing or mirroring)", shortcut: getKeyLabel('') }, { desc: "Rotate rectangle, polygon, polyline", shortcut: getKeyLabel('') }, { desc: "Translate text area", shortcut: getKeyLabel('') }, { desc: "Extend circle to ellipse", shortcut: getKeyLabel('') }, @@ -304,22 +304,6 @@ const PrefsPage = new GObject.Class({ listBox.add(otherBox); } - let controlBox = new Gtk.Box({ margin: MARGIN, margin_top: 2*MARGIN }); - let controlLabel = new Gtk.Label({ - wrap: true, - xalign: 0, - use_markup: true, - label: _("By pressing Ctrl key during 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)\n" + - " . smooth a free drawing stroke (you may prefer to smooth the stroke afterward, see “%s”)").format(_("Smooth last brushstroke")) - }); - controlLabel.set_halign(1); - controlLabel.get_style_context().add_class('dim-label'); - controlBox.pack_start(controlLabel, true, true, 4); - listBox.add(controlBox); - let internalKeybindingsWidget = new KeybindingsWidget(INTERNAL_KEYBINDINGS, this.settings); internalKeybindingsWidget.margin = MARGIN; listBox.add(internalKeybindingsWidget); From 75c20c34d20c33f5756cb779f1579e48112a9abf Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 22:31:31 +0200 Subject: [PATCH 60/67] Fix elementGrabberHandler * Do not start several handler. * Do not stop handler when toggling drawing mode. --- draw.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/draw.js b/draw.js index a926fd6..24152a9 100644 --- a/draw.js +++ b/draw.js @@ -443,6 +443,9 @@ var DrawingArea = new Lang.Class({ }, _startElementGrabber: function() { + if (this.elementGrabberHandler) + return; + this.elementGrabberHandler = this.connect('motion-event', (actor, event) => { if (this.motionHandler || this.grabbedElementLocked) { this.grabPoint = null; @@ -1006,10 +1009,6 @@ var DrawingArea = new Lang.Class({ this.disconnect(this._onKeyboardPopupMenuHandler); this._onKeyboardPopupMenuHandler = null; } - if (this.elementGrabberHandler) { - this.disconnect(this.elementGrabberHandler); - this.elementGrabberHandler = null; - } if (this.motionHandler) { this.disconnect(this.motionHandler); this.motionHandler = null; From f02d076ce2582c7f4af8e9848deca9a3f3038b82 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 27 Jun 2020 22:35:43 +0200 Subject: [PATCH 61/67] GS 3.24 compatibility --- draw.js | 21 ++++++++++++++------- extension.js | 12 ++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/draw.js b/draw.js index 24152a9..e8a8fd1 100644 --- a/draw.js +++ b/draw.js @@ -65,9 +65,11 @@ const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path(); const reverseEnumeration = function(obj) { - return Object.fromEntries(Object.entries(obj).map(entry => - [entry[1], entry[0].slice(0,1) + entry[0].slice(1).toLowerCase().replace('_', '-')] - )); + let reversed = {}; + Object.keys(obj).forEach(key => { + reversed[obj[key]] = key.slice(0,1) + key.slice(1).toLowerCase().replace('_', '-'); + }); + return reversed; }; const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 }; @@ -182,12 +184,17 @@ var DrawingArea = new Lang.Class({ set currentTool(tool) { this._currentTool = tool; - if (Object.values(Manipulations).indexOf(tool) != -1) + if (this.hasManipulationTool) this._startElementGrabber(); else this._stopElementGrabber(); }, + get hasManipulationTool() { + // No Object.values method in GS 3.24. + return Object.keys(Manipulations).map(key => Manipulations[key]).indexOf(this.currentTool) != -1; + }, + // Boolean wrapper for switch menu item. get currentEvenodd() { return this.currentFillRule == Cairo.FillRule.EVEN_ODD; @@ -254,7 +261,7 @@ var DrawingArea = new Lang.Class({ this.newThemeAttributes.LineJoin = ([0, 1, 2].indexOf(this.newThemeAttributes.LineJoin) != -1) ? this.newThemeAttributes.LineJoin : Cairo.LineJoin.ROUND; this.newThemeAttributes.LineCap = ([0, 1, 2].indexOf(this.newThemeAttributes.LineCap) != -1) ? this.newThemeAttributes.LineCap : Cairo.LineCap.ROUND; this.newThemeAttributes.FillRule = ([0, 1].indexOf(this.newThemeAttributes.FillRule) != -1) ? this.newThemeAttributes.FillRule : Cairo.FillRule.WINDING; - for (const attributeName in this.newThemeAttributes) { + for (let attributeName in this.newThemeAttributes) { if (this.newThemeAttributes[attributeName] != this.oldThemeAttributes[attributeName]) { this.oldThemeAttributes[attributeName] = this.newThemeAttributes[attributeName]; this[`current${attributeName}`] = this.newThemeAttributes[attributeName]; @@ -344,7 +351,7 @@ var DrawingArea = new Lang.Class({ } if (button == 1) { - if (Object.values(Manipulations).indexOf(this.currentTool) != -1) { + if (this.hasManipulationTool) { if (this.grabbedElement) this._startTransforming(x, y, controlPressed, shiftPressed); } else { @@ -755,7 +762,7 @@ var DrawingArea = new Lang.Class({ updatePointerCursor: function(controlPressed) { if (this.currentTool == Manipulations.MIRROR && this.grabbedElementLocked) this.setPointerCursor('CROSSHAIR'); - else if (Object.values(Manipulations).indexOf(this.currentTool) != -1) + else if (this.hasManipulationTool) this.setPointerCursor(this.grabbedElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.isWriting)) this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); diff --git a/extension.js b/extension.js index 96e3551..1c2ba1e 100644 --- a/extension.js +++ b/extension.js @@ -421,14 +421,10 @@ var AreaManager = new Lang.Class({ return; let hideTimeoutSave; - if (long) - try { - hideTimeoutSave = OsdWindow.HIDE_TIMEOUT; - OsdWindow.HIDE_TIMEOUT = HIDE_TIMEOUT_LONG; - } catch(e) { - // HIDE_TIMEOUT is not exportable. - hideTimeoutSave = null; - } + if (long && GS_VERSION >= '3.28.0') { + hideTimeoutSave = OsdWindow.HIDE_TIMEOUT; + OsdWindow.HIDE_TIMEOUT = HIDE_TIMEOUT_LONG; + } let maxLevel; if (level == -1) From be36fd4de2e4b4b26b3904c6df42a1c4caae520d Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 28 Jun 2020 13:10:31 +0200 Subject: [PATCH 62/67] Prefs, notifications and pot file * Add a long timeout for some OSD notifications. * Change some pref strings. * "Stroke" -> "Outline" * Clean pot file --- draw.js | 44 ++++++++++--------- extension.js | 14 +++--- locale/draw-on-your-screen.pot | 38 ++++------------ prefs.js | 13 +++--- ...extensions.draw-on-your-screen.gschema.xml | 10 ++--- 5 files changed, 51 insertions(+), 68 deletions(-) diff --git a/draw.js b/draw.js index e8a8fd1..42b00ee 100644 --- a/draw.js +++ b/draw.js @@ -130,7 +130,7 @@ const getJsonFiles = function() { var DrawingArea = new Lang.Class({ Name: 'DrawOnYourScreenDrawingArea', Extends: St.DrawingArea, - Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] }, + Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, 'update-action-mode': {}, 'leave-drawing-mode': {} }, @@ -397,8 +397,8 @@ var DrawingArea = new Lang.Class({ event.get_key_symbol() == Clutter.KEY_KP_Enter || event.get_key_symbol() == Clutter.KEY_Control_L) { if (this.currentElement.points.length == 2) - this.emit('show-osd', null, _("Press %s to get a fourth control point") - .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); + this.emit('show-osd', null, _("Press %s to get\na fourth control point") + .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); this.currentElement.addPoint(); this.updatePointerCursor(true); this._redisplay(); @@ -494,7 +494,7 @@ var DrawingArea = new Lang.Class({ if (this.grabbedElementLocked) { this.updatePointerCursor(); let label = controlPressed ? _("Mark a point of symmetry") : _("Draw a line of symmetry"); - this.emit('show-osd', null, label, "", -1); + this.emit('show-osd', null, label, "", -1, true); return; } } @@ -617,7 +617,8 @@ var DrawingArea = new Lang.Class({ this.currentElement.startDrawing(startX, startY); if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE) - this.emit('show-osd', null, _("Press %s to mark vertices").format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1); + this.emit('show-osd', null, _("Press %s to mark vertices") + .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); this.motionHandler = this.connect('motion-event', (actor, event) => { if (this.spaceKeyPressed) @@ -676,7 +677,8 @@ var DrawingArea = new Lang.Class({ _startWriting: function() { this.currentElement.text = ''; this.currentElement.cursorPosition = 0; - this.emit('show-osd', null, _("Type your text and press %s").format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1); + this.emit('show-osd', null, _("Type your text and press %s") + .format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1, true); this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); @@ -873,43 +875,43 @@ var DrawingArea = new Lang.Class({ this._redisplay(); } // Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost. - this.emit('show-osd', null, this.currentColor.to_string(), this.currentColor.to_string().slice(0, 7), -1); + this.emit('show-osd', null, this.currentColor.to_string(), this.currentColor.to_string().slice(0, 7), -1, false); }, selectTool: function(tool) { this.currentTool = tool; - this.emit('show-osd', null, _(ToolNames[tool]), "", -1); + this.emit('show-osd', null, _(ToolNames[tool]), "", -1, false); this.updatePointerCursor(); }, toggleFill: function() { this.fill = !this.fill; - this.emit('show-osd', null, this.fill ? _("Fill") : _("Stroke"), "", -1); + this.emit('show-osd', null, this.fill ? _("Fill") : _("Outline"), "", -1, false); }, toggleDash: function() { this.dashedLine = !this.dashedLine; - this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), "", -1); + this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), "", -1, false); }, incrementLineWidth: function(increment) { this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0); - this.emit('show-osd', null, _("%d px").format(this.currentLineWidth), "", 2 * this.currentLineWidth); + this.emit('show-osd', null, _("%d px").format(this.currentLineWidth), "", 2 * this.currentLineWidth, false); }, toggleLineJoin: function() { this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1; - this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), "", -1); + this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), "", -1, false); }, toggleLineCap: function() { this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1; - this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), "", -1); + this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), "", -1, false); }, toggleFillRule: function() { this.currentFillRule = this.currentFillRule == 1 ? 0 : this.currentFillRule + 1; - this.emit('show-osd', null, _(FillRuleNames[this.currentFillRule]), "", -1); + this.emit('show-osd', null, _(FillRuleNames[this.currentFillRule]), "", -1, false); }, toggleFontWeight: function() { @@ -920,7 +922,8 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.weight = this.currentFontWeight; this._redisplay(); } - this.emit('show-osd', null, `${_(FontWeightNames[this.currentFontWeight])}`, "", -1); + this.emit('show-osd', null, `` + + `${_(FontWeightNames[this.currentFontWeight])}`, "", -1, false); }, toggleFontStyle: function() { @@ -929,7 +932,8 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.style = this.currentFontStyle; this._redisplay(); } - this.emit('show-osd', null, `${_(FontStyleNames[this.currentFontStyle])}`, "", -1); + this.emit('show-osd', null, `` + + `${_(FontStyleNames[this.currentFontStyle])}`, "", -1, false); }, toggleFontFamily: function() { @@ -939,7 +943,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.family = currentFontFamily; this._redisplay(); } - this.emit('show-osd', null, `${_(currentFontFamily)}`, "", -1); + this.emit('show-osd', null, `${_(currentFontFamily)}`, "", -1, false); }, toggleTextAlignment: function() { @@ -948,7 +952,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.textRightAligned = this.currentTextRightAligned; this._redisplay(); } - this.emit('show-osd', null, this.currentTextRightAligned ? _("Right aligned") : _("Left aligned"), "", -1); + this.emit('show-osd', null, this.currentTextRightAligned ? _("Right aligned") : _("Left aligned"), "", -1, false); }, toggleHelp: function() { @@ -1119,7 +1123,7 @@ var DrawingArea = new Lang.Class({ GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { GLib.file_set_contents(path, contents); if (notify) - this.emit('show-osd', 'document-save-symbolic', name, "", -1); + this.emit('show-osd', 'document-save-symbolic', name, "", -1, false); if (name != Me.metadata['persistent-file-name']) { this.jsonName = name; this.lastJsonContents = contents; @@ -1171,7 +1175,7 @@ var DrawingArea = new Lang.Class({ this.elements.push(...JSON.parse(contents).map(object => new DrawingElement(object))); if (notify) - this.emit('show-osd', 'document-open-symbolic', name, "", -1); + this.emit('show-osd', 'document-open-symbolic', name, "", -1, false); if (name != Me.metadata['persistent-file-name']) { this.jsonName = name; this.lastJsonContents = contents; diff --git a/extension.js b/extension.js index 1c2ba1e..f0c0a48 100644 --- a/extension.js +++ b/extension.js @@ -81,18 +81,18 @@ var AreaManager = new Lang.Class({ Shell.ActionMode.ALL, this.toggleDrawing.bind(this)); - Main.wm.addKeybinding('erase-drawing', - this.settings, - Meta.KeyBindingFlags.NONE, - Shell.ActionMode.ALL, - this.eraseDrawing.bind(this)); - Main.wm.addKeybinding('toggle-modal', this.settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.ALL, this.toggleModal.bind(this)); + Main.wm.addKeybinding('erase-drawing', + this.settings, + Meta.KeyBindingFlags.NONE, + Shell.ActionMode.ALL, + this.eraseDrawing.bind(this)); + this.updateAreas(); this.monitorChangedHandler = Main.layoutManager.connect('monitors-changed', this.updateAreas.bind(this)); @@ -536,8 +536,8 @@ var AreaManager = new Lang.Class({ if (this.activeArea) this.toggleDrawing(); Main.wm.removeKeybinding('toggle-drawing'); - Main.wm.removeKeybinding('erase-drawing'); Main.wm.removeKeybinding('toggle-modal'); + Main.wm.removeKeybinding('erase-drawing'); this.removeAreas(); if (this.indicator) this.indicator.disable(); diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 4b6a44e..f3aa818 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Draw On Your Screen VERSION\n" "Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" -"POT-Creation-Date: 2020-01-03 08:00+0100\n" +"POT-Creation-Date: 2019-03-04 16:40+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -74,7 +74,9 @@ msgstr "" msgid "Mirror" msgstr "" -msgid "Press %s to get a fourth control point" +msgid "" +"Press %s to get\n" +"a fourth control point" msgstr "" msgid "Mark a point of symmetry" @@ -115,7 +117,6 @@ msgid "Left aligned" msgstr "" #: helper - msgid "Screenshot" msgstr "" @@ -132,7 +133,6 @@ msgid "System" msgstr "" #: menu - msgid "Undo" msgstr "" @@ -162,7 +162,6 @@ msgid "Save drawing" msgstr "" #: prefs.js - msgid "Preferences" msgstr "" @@ -170,18 +169,16 @@ msgid "About" msgstr "" #: GLOBAL_KEYBINDINGS - msgid "Enter/leave drawing mode" msgstr "" -msgid "Erase all drawings" -msgstr "" - msgid "Toggle modeless/modal" msgstr "" -#: INTERNAL_KEYBINDINGS +msgid "Erase all drawings" +msgstr "" +#: INTERNAL_KEYBINDINGS msgid "Undo last brushstroke" msgstr "" @@ -194,9 +191,6 @@ msgstr "" msgid "Smooth last brushstroke" msgstr "" -msgid "Free drawing" -msgstr "" - msgid "Select line" msgstr "" @@ -248,10 +242,6 @@ msgstr "" msgid "Toggle fill rule" msgstr "" -#: already in draw.js -#:msgid "Dashed line" -#:msgstr "" - msgid "Change font family (generic name)" msgstr "" @@ -282,10 +272,6 @@ msgstr "" msgid "Open next drawing" msgstr "" -#: already in draw.js -#:msgid "Save drawing" -#:msgstr "" - msgid "Save drawing as a SVG file" msgstr "" @@ -299,7 +285,6 @@ msgid "Show help" msgstr "" #: OTHER_SHORTCUTS - msgid "Draw" msgstr "" @@ -341,15 +326,12 @@ msgstr "" msgid "Select eraser (while starting drawing)" msgstr "" -msgid "Duplicate (while starting moving, resizing or mirroring)" +msgid "Duplicate (while starting handling)" msgstr "" msgid "Rotate rectangle, polygon, polyline" msgstr "" -msgid "Translate text area" -msgstr "" - msgid "Extend circle to ellipse" msgstr "" @@ -369,7 +351,6 @@ msgid "Inverse (while mirroring)" msgstr "" #: About page - # You are free to translate the extension name, that is displayed in About page, or not. msgid "Draw On You Screen" msgstr "" @@ -381,7 +362,6 @@ msgid "Start drawing with Super+Alt+D and save your beautiful work by taking a s msgstr "" #: Prefs page - msgid "Global" msgstr "" @@ -415,7 +395,7 @@ msgid "" msgstr "" msgid "" -"Note: When you save elements made with eraser in a SVG file, " +"When you save elements made with eraser in a SVG file, " "they are colored with background color, transparent if it is disabled.\n" "See “%s” or edit the SVG file afterwards." msgstr "" diff --git a/prefs.js b/prefs.js index d4a7837..57c5388 100644 --- a/prefs.js +++ b/prefs.js @@ -37,8 +37,8 @@ const MARGIN = 10; var GLOBAL_KEYBINDINGS = { 'toggle-drawing': "Enter/leave drawing mode", - 'erase-drawing': "Erase all drawings", - 'toggle-modal': "Toggle modeless/modal" + 'toggle-modal': "Toggle modeless/modal", + 'erase-drawing': "Erase all drawings" }; var INTERNAL_KEYBINDINGS = { @@ -58,7 +58,7 @@ var INTERNAL_KEYBINDINGS = { 'select-resize-tool': "Select resize", 'select-mirror-tool': "Select mirror", '-separator-2': '', - 'toggle-fill': "Toggle fill/stroke", + 'toggle-fill': "Toggle fill/outline", 'toggle-fill-rule': "Toggle fill rule", '-separator-3': '', 'increment-line-width': "Increment line width", @@ -99,16 +99,15 @@ function getKeyLabel(accel) { var OTHER_SHORTCUTS = [ { desc: "Draw", get shortcut() { return _("Left click"); } }, { desc: "Menu", get shortcut() { return _("Right click"); } }, - { desc: "Toggle fill/stroke", get shortcut() { return _("Center click"); } }, + { desc: "Toggle fill/outline", get shortcut() { return _("Center click"); } }, { desc: "Increment/decrement line width", get shortcut() { return _("Scroll"); } }, { desc: "Select color", get shortcut() { return _("%s … %s").format(getKeyLabel('1'), getKeyLabel('9')); } }, { desc: "Ignore pointer movement", get shortcut() { return _("%s held").format(getKeyLabel('space')); } }, { desc: "Leave", shortcut: getKeyLabel('Escape') }, { desc: "-separator-1", shortcut: "" }, { desc: "Select eraser (while starting drawing)", shortcut: getKeyLabel('') }, - { desc: "Duplicate (while starting moving, resizing or mirroring)", shortcut: getKeyLabel('') }, + { desc: "Duplicate (while starting handling)", shortcut: getKeyLabel('') }, { desc: "Rotate rectangle, polygon, polyline", shortcut: getKeyLabel('') }, - { desc: "Translate text area", shortcut: getKeyLabel('') }, { desc: "Extend circle to ellipse", shortcut: getKeyLabel('') }, { desc: "Curve line", shortcut: getKeyLabel('') }, { desc: "Smooth free drawing stroke", shortcut: getKeyLabel('') }, @@ -326,7 +325,7 @@ const PrefsPage = new GObject.Class({ wrap: true, xalign: 0, use_markup: true, - label: _("Note: When you save elements made with eraser in a SVG file, " + + label: _("When you save elements made with eraser in a SVG file, " + "they are colored with background color, transparent if it is disabled.\n" + "See “%s” or edit the SVG file afterwards.").format(_("Add a drawing background")) }); diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml index e9cb2bf..767f859 100644 --- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml +++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml @@ -26,16 +26,16 @@ toggle drawing enter or leave drawing mode - - ["<Alt><Super>e"] - erase drawing - erase drawing - ["<Primary><Alt><Super>d"] toggle modeless/modal toggle modeless/modal + + ["<Alt><Super>e"] + erase drawing + erase drawing + ["<Primary>z"] undo From 9199fbd82b25924ac9d88366daa172182c2893c9 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 28 Jun 2020 18:28:15 +0200 Subject: [PATCH 63/67] Reorder pot file --- locale/draw-on-your-screen.pot | 592 +++++++++++++++++---------------- 1 file changed, 301 insertions(+), 291 deletions(-) diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index f3aa818..7fce215 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -4,6 +4,8 @@ # This file is distributed under the same license as Draw On Your Screen. # FIRST AUTHOR , YEAR. # +# Some words refer to SVG attributes (font, line, fill rule ...). +# You are free to translate them or not. msgid "" msgstr "" "Project-Id-Version: Draw On Your Screen VERSION\n" @@ -17,6 +19,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +msgid "About" +msgstr "" + +# You are free to translate the extension name, that is displayed in About page, or not. +msgid "Draw On You Screen" +msgstr "" + +msgid "Version %d" +msgstr "" + +msgid "Start drawing with Super+Alt+D and save your beautiful work by taking a screenshot" +msgstr "" + # Add your name here, for example: # (add "\n" as separator if there is many translators) # msgid "translator-credits" @@ -32,143 +47,12 @@ msgstr "" msgid "translator-credits" msgstr "" -#: extension.js -msgid "Leaving drawing mode" -msgstr "" - -# %s is a key label -msgid "Press %s for help" -msgstr "" - -msgid "Entering drawing mode" -msgstr "" - -#: draw.js -msgid "Free drawing" -msgstr "" - -msgid "Line" -msgstr "" - -msgid "Ellipse" -msgstr "" - -msgid "Rectangle" -msgstr "" - -msgid "Text" -msgstr "" - -msgid "Polygon" -msgstr "" - -msgid "Polyline" -msgstr "" - -msgid "Move" -msgstr "" - -msgid "Resize" -msgstr "" - -msgid "Mirror" -msgstr "" - -msgid "" -"Press %s to get\n" -"a fourth control point" -msgstr "" - -msgid "Mark a point of symmetry" -msgstr "" - -msgid "Draw a line of symmetry" -msgstr "" - -# %s is a key label -msgid "" -"Press %s to mark vertices" -msgstr "" - -# %s is a key label -msgid "" -"Type your text and press %s" -msgstr "" - -msgid "Fill" -msgstr "" - -msgid "Stroke" -msgstr "" - -msgid "Dashed line" -msgstr "" - -msgid "Full line" -msgstr "" - -msgid "%d px" -msgstr "" - -msgid "Right aligned" -msgstr "" - -msgid "Left aligned" -msgstr "" - -#: helper -msgid "Screenshot" -msgstr "" - -msgid "Screenshot to clipboard" -msgstr "" - -msgid "Area screenshot" -msgstr "" - -msgid "Area screenshot to clipboard" -msgstr "" - -msgid "System" -msgstr "" - -#: menu -msgid "Undo" -msgstr "" - -msgid "Redo" -msgstr "" - -msgid "Erase" -msgstr "" - -msgid "Smooth" -msgstr "" - -# Evenodd is a fill rule SVG attribute (even as 2,4,6,... and odd as 1,3,5,...) -msgid "Evenodd" -msgstr "" - -msgid "Dashed" -msgstr "" - -msgid "Color" -msgstr "" - -msgid "Open drawing" -msgstr "" - -msgid "Save drawing" -msgstr "" - -#: prefs.js msgid "Preferences" msgstr "" -msgid "About" +msgid "Global" msgstr "" -#: GLOBAL_KEYBINDINGS msgid "Enter/leave drawing mode" msgstr "" @@ -178,7 +62,95 @@ msgstr "" msgid "Erase all drawings" msgstr "" -#: INTERNAL_KEYBINDINGS +msgid "Persistent" +msgstr "" + +msgid "Persistent drawing through session restart" +msgstr "" + +msgid "Drawing on the desktop" +msgstr "" + +msgid "Draw On Your Screen becomes Draw On Your Desktop" +msgstr "" + +msgid "Disable on-screen notifications" +msgstr "" + +msgid "Disable panel indicator" +msgstr "" + +msgid "Draw" +msgstr "" + +msgid "Left click" +msgstr "" + +msgid "Menu" +msgstr "" + +msgid "Right click" +msgstr "" + +msgid "Center click" +msgstr "" + +msgid "Increment/decrement line width" +msgstr "" + +msgid "Scroll" +msgstr "" + +msgid "Select color" +msgstr "" + +# %s are key labels (Ctrl+F1 and Ctrl+F9) +msgid "%s … %s" +msgstr "" + +msgid "Ignore pointer movement" +msgstr "" + +# %s is a key label +msgid "%s held" +msgstr "" + +msgid "Leave" +msgstr "" + +msgid "Select eraser (while starting drawing)" +msgstr "" + +msgid "Duplicate (while starting handling)" +msgstr "" + +msgid "Rotate rectangle, polygon, polyline" +msgstr "" + +msgid "Extend circle to ellipse" +msgstr "" + +msgid "Curve line" +msgstr "" + +msgid "Smooth free drawing stroke" +msgstr "" + +msgid "Rotate (while moving)" +msgstr "" + +msgid "Stretch (while resizing)" +msgstr "" + +msgid "Inverse (while mirroring)" +msgstr "" + +msgid "Internal" +msgstr "" + +msgid "(in drawing mode)" +msgstr "" + msgid "Undo last brushstroke" msgstr "" @@ -284,111 +256,6 @@ msgstr "" msgid "Show help" msgstr "" -#: OTHER_SHORTCUTS -msgid "Draw" -msgstr "" - -msgid "Left click" -msgstr "" - -msgid "Menu" -msgstr "" - -msgid "Right click" -msgstr "" - -msgid "Center click" -msgstr "" - -msgid "Increment/decrement line width" -msgstr "" - -msgid "Scroll" -msgstr "" - -msgid "Select color" -msgstr "" - -# %s are key labels (Ctrl+F1 and Ctrl+F9) -msgid "%s … %s" -msgstr "" - -msgid "Ignore pointer movement" -msgstr "" - -# %s is a key label -msgid "%s held" -msgstr "" - -msgid "Leave" -msgstr "" - -msgid "Select eraser (while starting drawing)" -msgstr "" - -msgid "Duplicate (while starting handling)" -msgstr "" - -msgid "Rotate rectangle, polygon, polyline" -msgstr "" - -msgid "Extend circle to ellipse" -msgstr "" - -msgid "Curve line" -msgstr "" - -msgid "Smooth free drawing stroke" -msgstr "" - -msgid "Rotate (while moving)" -msgstr "" - -msgid "Stretch (while resizing)" -msgstr "" - -msgid "Inverse (while mirroring)" -msgstr "" - -#: About page -# You are free to translate the extension name, that is displayed in About page, or not. -msgid "Draw On You Screen" -msgstr "" - -msgid "Version %d" -msgstr "" - -msgid "Start drawing with Super+Alt+D and save your beautiful work by taking a screenshot" -msgstr "" - -#: Prefs page -msgid "Global" -msgstr "" - -msgid "Persistent" -msgstr "" - -msgid "Persistent drawing through session restart" -msgstr "" - -msgid "Drawing on the desktop" -msgstr "" - -msgid "Draw On Your Screen becomes Draw On Your Desktop" -msgstr "" - -msgid "Disable on-screen notifications" -msgstr "" - -msgid "Disable panel indicator" -msgstr "" - -msgid "Internal" -msgstr "" - -msgid "(in drawing mode)" -msgstr "" - msgid "" "Default drawing style attributes (color palette, font, line, dash) are defined in an editable css file.\n" "See “%s”." @@ -400,83 +267,226 @@ msgid "" "See “%s” or edit the SVG file afterwards." msgstr "" +msgid "Screenshot" +msgstr "" -# The following words refer to SVG attributes. -# You are free to translate them or not. +msgid "Screenshot to clipboard" +msgstr "" -#msgid "Butt" -#msgstr "" +msgid "Area screenshot" +msgstr "" -#msgid "Round" -#msgstr "" +msgid "Area screenshot to clipboard" +msgstr "" -#msgid "Square" -#msgstr "" +msgid "System" +msgstr "" -#msgid "Miter" -#msgstr "" +msgid "Undo" +msgstr "" -#msgid "Bevel" -#msgstr "" +msgid "Redo" +msgstr "" -#msgid "Nonzero" -#msgstr "" +msgid "Erase" +msgstr "" -#already in menu -#msgid "Evenodd" -#msgstr "" +msgid "Smooth" +msgstr "" -#msgid "Thin" -#msgstr "" +msgid "Free drawing" +msgstr "" -#msgid "Ultra-light" -#msgstr "" +msgid "Line" +msgstr "" -#msgid "Light" -#msgstr "" +msgid "Ellipse" +msgstr "" -#msgid "Semi-light" -#msgstr "" +msgid "Rectangle" +msgstr "" -#msgid "Book" -#msgstr "" +msgid "Text" +msgstr "" -#msgid "Normal" -#msgstr "" +msgid "Polygon" +msgstr "" -#msgid "Medium" -#msgstr "" +msgid "Polyline" +msgstr "" -#msgid "Semi-bold" -#msgstr "" +msgid "Move" +msgstr "" -#msgid "Bold" -#msgstr "" +msgid "Resize" +msgstr "" -#msgid "Ultra-bold" -#msgstr "" +msgid "Mirror" +msgstr "" -#msgid "Heavy" -#msgstr "" +msgid "Color" +msgstr "" -#msgid "Italic" -#msgstr "" +msgid "Fill" +msgstr "" -#msgid "Oblique" -#msgstr "" +# fill-rule SVG attribute +msgid "Evenodd" +msgstr "" -#msgid "Sans-Serif" -#msgstr "" +msgid "%d px" +msgstr "" -#msgid "Serif" -#msgstr "" +# stroke-linejoin SVG attribute +msgid "Miter" +msgstr "" -#msgid "Monospace" -#msgstr "" +# stroke-linejoin and stroke-linecap SVG attribute +msgid "Round" +msgstr "" -#msgid "Cursive" -#msgstr "" +# stroke-linejoin SVG attribute +msgid "Bevel" +msgstr "" + +# stroke-linecap SVG attribute +msgid "Butt" +msgstr "" + +# stroke-linecap SVG attribute +msgid "Square" +msgstr "" + +msgid "Dashed" +msgstr "" + +# generic font-family SVG attribute +msgid "Sans-Serif" +msgstr "" + +# generic font-family SVG attribute +msgid "Serif" +msgstr "" + +# generic font-family SVG attribute +msgid "Monospace" +msgstr "" + +# generic font-family SVG attribute +msgid "Cursive" +msgstr "" + +# generic font-family SVG attribute +msgid "Fantasy" +msgstr "" + +# font-weight SVG attribute +msgid "Thin" +msgstr "" + +# font-weight SVG attribute +msgid "Ultra-light" +msgstr "" + +# font-weight SVG attribute +msgid "Light" +msgstr "" + +# font-weight SVG attribute +msgid "Semi-light" +msgstr "" + +# font-weight SVG attribute +msgid "Book" +msgstr "" + +# font-weight and font-style SVG attribute +msgid "Normal" +msgstr "" + +# font-weight SVG attribute +msgid "Medium" +msgstr "" + +# font-weight SVG attribute +msgid "Semi-bold" +msgstr "" + +# font-weight SVG attribute +msgid "Bold" +msgstr "" + +# font-weight SVG attribute +msgid "Ultra-bold" +msgstr "" + +# font-weight SVG attribute +msgid "Heavy" +msgstr "" + +# font-style SVG attribute +msgid "Italic" +msgstr "" + +# font-style SVG attribute +msgid "Oblique" +msgstr "" + +msgid "Right aligned" +msgstr "" + +msgid "Open drawing" +msgstr "" + +msgid "Save drawing" +msgstr "" + +msgid "Leaving drawing mode" +msgstr "" + +# %s is a key label +msgid "Press %s for help" +msgstr "" + +msgid "Entering drawing mode" +msgstr "" + +# %s is a key label +msgid "" +"Press %s to get\n" +"a fourth control point" +msgstr "" + +msgid "Mark a point of symmetry" +msgstr "" + +msgid "Draw a line of symmetry" +msgstr "" + +# %s is a key label +msgid "" +"Press %s to mark vertices" +msgstr "" + +# %s is a key label +msgid "" +"Type your text and press %s" +msgstr "" + +# as the alternative to "Fill" +msgid "Outline" +msgstr "" + +msgid "Dashed line" +msgstr "" + +msgid "Full line" +msgstr "" + +msgid "Left aligned" +msgstr "" + +msgid "Nonzero" +msgstr "" -#msgid "Fantasy" -#msgstr "" From 0d42d69f91c7225db1ad5b4ad7539b2e24fcaa3d Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 28 Jun 2020 18:37:42 +0200 Subject: [PATCH 64/67] Pointer cursor when writing --- draw.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/draw.js b/draw.js index 42b00ee..087f504 100644 --- a/draw.js +++ b/draw.js @@ -682,12 +682,12 @@ var DrawingArea = new Lang.Class({ this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); - this.updatePointerCursor(); this.textEntry = new St.Entry({ visible: false }); this.get_parent().add_child(this.textEntry); this.textEntry.grab_key_focus(); this.updateActionMode(); + this.updatePointerCursor(); this.textEntry.clutterText.connect('activate', (clutterText) => { let startNewLine = true; @@ -749,6 +749,7 @@ var DrawingArea = new Lang.Class({ delete this.textEntry; this.grab_key_focus(); this.updateActionMode(); + this.updatePointerCursor(); } this._redisplay(); @@ -766,7 +767,9 @@ var DrawingArea = new Lang.Class({ this.setPointerCursor('CROSSHAIR'); else if (this.hasManipulationTool) this.setPointerCursor(this.grabbedElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); - else if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.isWriting)) + else if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) + this.setPointerCursor('IBEAM'); + else if (!this.currentElement) this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); else if (this.currentElement.shape != Shapes.NONE && controlPressed) this.setPointerCursor('MOVE_OR_RESIZE_WINDOW'); From e50a8c80a7a2312f69c8e839935bba58e662497b Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 29 Jun 2020 12:21:21 +0200 Subject: [PATCH 65/67] "Modeless/modal" -> "Grab/ungrab keyboard and pointer" And add an OSD notification. --- README.md | 2 +- extension.js | 6 +++++- locale/draw-on-your-screen.pot | 10 +++++++++- prefs.js | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c88b33f..0a95723 100644 --- a/README.md +++ b/README.md @@ -35,5 +35,5 @@ Then save your beautiful work by taking a screenshot. * Screenshot Tool extension: - [Screenshot Tool](https://extensions.gnome.org/extension/1112/screenshot-tool/) is a convenient extension to “create, copy, store and upload screenshots”. To use it while drawing mode is active, toggle the area selection mode thanks to the Screenshot Tool shortcut (`Super + F11` by default, see its preferences) and **hold** the `space` key when selecting the area with pointer to avoid drawing. + [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`). diff --git a/extension.js b/extension.js index f0c0a48..2f42a55 100644 --- a/extension.js +++ b/extension.js @@ -351,7 +351,7 @@ var AreaManager = new Lang.Class({ } }, - toggleModal: function() { + toggleModal: function(source) { if (!this.activeArea) return; @@ -359,6 +359,8 @@ var AreaManager = new Lang.Class({ if (Main._findModal(this.activeArea) != -1) { Main.popModal(this.activeArea); + if (source && source == global.display) + this.showOsd(null, 'touchpad-disabled-symbolic', _("Keyboard and pointer released"), null, null, false); setCursor('DEFAULT'); this.activeArea.reactive = false; this.removeInternalKeybindings(); @@ -370,6 +372,8 @@ var AreaManager = new Lang.Class({ this.addInternalKeybindings(); this.activeArea.reactive = true; this.activeArea.initPointerCursor(); + if (source && source == global.display) + this.showOsd(null, 'input-touchpad-symbolic', _("Keyboard and pointer grabbed"), null, null, false); } return true; diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 7fce215..ee4bf35 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -56,7 +56,8 @@ msgstr "" msgid "Enter/leave drawing mode" msgstr "" -msgid "Toggle modeless/modal" +# There is a similar text in GNOME Boxes (https://gitlab.gnome.org/GNOME/gnome-boxes/tree/master/po) +msgid "Grab/ungrab keyboard and pointer" msgstr "" msgid "Erase all drawings" @@ -451,6 +452,13 @@ msgstr "" msgid "Entering drawing mode" msgstr "" +# "released" as the opposite of "grabbed" +msgid "Keyboard and pointer released" +msgstr "" + +msgid "Keyboard and pointer grabbed" +msgstr "" + # %s is a key label msgid "" "Press %s to get\n" diff --git a/prefs.js b/prefs.js index 57c5388..ed47d98 100644 --- a/prefs.js +++ b/prefs.js @@ -37,7 +37,7 @@ const MARGIN = 10; var GLOBAL_KEYBINDINGS = { 'toggle-drawing': "Enter/leave drawing mode", - 'toggle-modal': "Toggle modeless/modal", + 'toggle-modal': "Grab/ungrab keyboard and pointer", 'erase-drawing': "Erase all drawings" }; From 3f0fce3bc669355bb255a90849ed3939709efd99 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 29 Jun 2020 15:40:37 +0200 Subject: [PATCH 66/67] Add "duplicate an element" how-to in readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 0a95723..7dda536 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,12 @@ Then save your beautiful work by taking a screenshot. ![How to draw an arrow](https://framagit.org/abakkk/DrawOnYourScreen/uploads/af8f96d33cfeff49bb922a1ef9f4a4ce/arrow-screencast.webm) +* Duplicate an element: + + Hold the `Shift` key while starting moving. + + ![How to duplicate an element](https://framagit.org/abakkk/DrawOnYourScreen/-/raw/ressources/duplicate.webm) + * 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`). From 040b0138bbeee986549aa4ba2dfc1e2ebdca24dd Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 29 Jun 2020 15:58:09 +0200 Subject: [PATCH 67/67] version -> v6.1 --- NEWS | 21 +++++++++++++++++++++ README.md | 1 + metadata.json | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index bab0ca3..fd428fd 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,24 @@ +v6.1 - June 2020 +================= + +* Fix empty media-keys settings case #28 +* Fix label color in OSD and menu #31 +* Fix OSD width +* Fix "Hide panel and dock" +* Small menu on small monitor +* Start a new text line with the `Enter` key #30 +* IBus inputs #29 #34 +* Non-latin and emoji characters rendered +* GS keyboard is now available to type text +* Grid overlay +* Polygon and polyline shapes +* Move, resize and mirror tools +* Fill rule and text alignment style attributes +* An optional fourth control point for Bézier curves +* Keybinding to toggle modeless/modal #6 #9 #20 #24 #33 + (ungrabs keyboard and mouse while drawing remains at the top) +* Attributes are now persistent through drawing mode toggling #27 + v6 - March 2020 ================= diff --git a/README.md b/README.md index 7dda536..f086e4c 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Then save your beautiful work by taking a screenshot. ## Features * Basic shapes (rectangle, circle, ellipse, line, curve, text, free) +* Basic transformations (move, rotate, resize, stretch, mirror, inverse) * Smooth stroke * Draw over applications * Keep drawings on desktop background with persistence (notes, children's art ...) diff --git a/metadata.json b/metadata.json index 9412998..49297dc 100644 --- a/metadata.json +++ b/metadata.json @@ -17,5 +17,5 @@ "3.34", "3.36" ], - "version": 6 + "version": 6.1 }