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 b90b102..ce20632 100644 Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ 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 7a3fab7..3c9cd0f 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 @@ -107,12 +107,12 @@ 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