From 04894a90311ab4beebba6dee2b43832204d27d19 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 8 Jun 2020 20:50:23 +0200 Subject: [PATCH] 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 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 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