From 4907e357787adffbdbb852c525163aa217262737 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 15 Jun 2020 22:13:03 +0200 Subject: [PATCH] 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