From 58677dc175f27d80a153ccb7eb5b9ed7b40df776 Mon Sep 17 00:00:00 2001 From: abakkk Date: Fri, 9 Oct 2020 11:51:01 +0200 Subject: [PATCH] Split the drawing area in several layer widgets. The drawing area is now a container and the layers are the proper drawing widgets. There is a separated layer for the current element so only the current element is redisplayed when drawing. --- area.js | 202 ++++++++++++++++++++++++++++++++------------------- extension.js | 41 +++++------ 2 files changed, 143 insertions(+), 100 deletions(-) diff --git a/area.js b/area.js index 6415d6f..0c226d9 100644 --- a/area.js +++ b/area.js @@ -83,12 +83,47 @@ const getColorFromString = function(string, fallback) { return color; }; -// DrawingArea is the widget in which we draw, thanks to Cairo. -// It creates and manages a DrawingElement for each "brushstroke". +// Drawing layers are the proper drawing area widgets (painted thanks to Cairo). +const DrawingLayer = new Lang.Class({ + Name: `${UUID}-DrawingLayer`, + Extends: St.DrawingArea, + + _init: function(repaintFunction, getHasImageFunction) { + this._repaint = repaintFunction; + this._getHasImage = getHasImageFunction || (() => false); + this.parent(); + }, + + // Bind the size of layers and layer container. + vfunc_parent_set: function() { + this.clear_constraints(); + + if (this.get_parent()) + this.add_constraint(new Clutter.BindConstraint({ coordinate: Clutter.BindCoordinate.SIZE, source: this.get_parent() })); + }, + + vfunc_repaint: function() { + let cr = this.get_context(); + + try { + this._repaint(cr); + } catch(e) { + logError(e, "An error occured while painting"); + } + + cr.$dispose(); + if (this._getHasImage()) + System.gc(); + } +}); + +// Darwing area is a container that manages drawing elements and drawing layers. +// There is a drawing element for each "brushstroke". +// There is a separated layer for the current element so only the current element is redisplayed when drawing. // It handles pointer/mouse/(touch?) events and some keyboard events. var DrawingArea = new Lang.Class({ Name: `${UUID}-DrawingArea`, - Extends: St.DrawingArea, + Extends: St.Widget, Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, 'update-action-mode': {}, 'leave-drawing-mode': {} }, @@ -98,6 +133,17 @@ var DrawingArea = new Lang.Class({ this.monitor = monitor; this.helper = helper; + this.layerContainer = new St.Widget({ width: monitor.width, height: monitor.height }); + this.add_child(this.layerContainer); + this.add_child(this.helper); + + this.backLayer = new DrawingLayer(this._repaintBack.bind(this), this._getHasImageBack.bind(this)); + this.layerContainer.add_child(this.backLayer); + this.foreLayer = new DrawingLayer(this._repaintFore.bind(this), this._getHasImageFore.bind(this)); + this.layerContainer.add_child(this.foreLayer); + this.gridLayer = new DrawingLayer(this._repaintGrid.bind(this)); + this.layerContainer.add_child(this.gridLayer); + this.elements = []; this.undoneElements = []; this.currentElement = null; @@ -225,25 +271,6 @@ var DrawingArea = new Lang.Class({ return this._fontFamilies; }, - vfunc_repaint: function() { - let cr = this.get_context(); - - try { - this._repaint(cr); - } catch(e) { - logError(e, "An error occured while painting"); - } - - cr.$dispose(); - if (this.elements.some(element => element.shape == Shapes.IMAGE) || this.currentElement && this.currentElement.shape == Shapes.IMAGE) - System.gc(); - }, - - _redisplay: function() { - // force area to emit 'repaint' - this.queue_repaint(); - }, - _onDrawingSettingsChanged: function() { this.palettes = Me.drawingSettings.get_value('palettes').deep_unpack(); if (!this.colors) { @@ -284,7 +311,7 @@ var DrawingArea = new Lang.Class({ } }, - _repaint: function(cr) { + _repaintBack: function(cr) { if (CAIRO_DEBUG_EXTENDS) { cr.scale(0.5, 0.5); cr.translate(this.monitor.width, this.monitor.height); @@ -308,42 +335,65 @@ var DrawingArea = new Lang.Class({ cr.stroke(); cr.restore(); } + }, + + _repaintFore: function(cr) { + if (!this.currentElement) + return; - if (this.currentElement) { - cr.save(); - this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showTextRectangle: this.currentElement.shape != Shapes.TEXT || !this.isWriting, - dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); - + this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, + showTextRectangle: this.currentElement.shape != Shapes.TEXT || !this.isWriting, + dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); + cr.stroke(); + }, + + _repaintGrid: function(cr) { + if (!this.reactive || !this.hasGrid) + return; + + Clutter.cairo_set_source_color(cr, this.gridColor); + + let [gridX, gridY] = [0, 0]; + while (gridX < this.monitor.width / 2) { + cr.setLineWidth((gridX / this.gridLineSpacing) % 5 ? this.gridLineWidth / 2 : this.gridLineWidth); + cr.moveTo(this.monitor.width / 2 + gridX, 0); + cr.lineTo(this.monitor.width / 2 + gridX, this.monitor.height); + cr.moveTo(this.monitor.width / 2 - gridX, 0); + cr.lineTo(this.monitor.width / 2 - gridX, this.monitor.height); + gridX += this.gridLineSpacing; cr.stroke(); - cr.restore(); } + while (gridY < this.monitor.height / 2) { + cr.setLineWidth((gridY / this.gridLineSpacing) % 5 ? this.gridLineWidth / 2 : this.gridLineWidth); + cr.moveTo(0, this.monitor.height / 2 + gridY); + cr.lineTo(this.monitor.width, this.monitor.height / 2 + gridY); + cr.moveTo(0, this.monitor.height / 2 - gridY); + cr.lineTo(this.monitor.width, this.monitor.height / 2 - gridY); + gridY += this.gridLineSpacing; + cr.stroke(); + } + }, + + _getHasImageBack: function() { + return this.elements.some(element => element.shape == Shapes.IMAGE); + }, + + _getHasImageFore: function() { + return this.currentElement && this.currentElement.shape == Shapes.IMAGE || false; + }, + + _redisplay: function() { + // force area to emit 'repaint' + this.backLayer.queue_repaint(); + this.foreLayer.queue_repaint(); + this.gridLayer.queue_repaint(); + }, + + _transformStagePoint: function(x, y) { + if (!this.layerContainer.get_allocation_box().contains(x, y)) + return [false, 0, 0]; - if (this.reactive && this.hasGrid) { - cr.save(); - Clutter.cairo_set_source_color(cr, this.gridColor); - - let [gridX, gridY] = [0, 0]; - while (gridX < this.monitor.width / 2) { - cr.setLineWidth((gridX / this.gridLineSpacing) % 5 ? this.gridLineWidth / 2 : this.gridLineWidth); - cr.moveTo(this.monitor.width / 2 + gridX, 0); - cr.lineTo(this.monitor.width / 2 + gridX, this.monitor.height); - cr.moveTo(this.monitor.width / 2 - gridX, 0); - cr.lineTo(this.monitor.width / 2 - gridX, this.monitor.height); - gridX += this.gridLineSpacing; - cr.stroke(); - } - while (gridY < this.monitor.height / 2) { - cr.setLineWidth((gridY / this.gridLineSpacing) % 5 ? this.gridLineWidth / 2 : this.gridLineWidth); - cr.moveTo(0, this.monitor.height / 2 + gridY); - cr.lineTo(this.monitor.width, this.monitor.height / 2 + gridY); - cr.moveTo(0, this.monitor.height / 2 - gridY); - cr.lineTo(this.monitor.width, this.monitor.height / 2 - gridY); - gridY += this.gridLineSpacing; - cr.stroke(); - } - cr.restore(); - } + return this.layerContainer.transform_stage_point(x, y); }, _onButtonPressed: function(actor, event) { @@ -479,7 +529,7 @@ var DrawingArea = new Lang.Class({ this.elementGrabberTimestamp = event.get_time(); let coords = event.get_coords(); - let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); + let [s, x, y] = this._transformStagePoint(coords[0], coords[1]); if (!s) return; @@ -499,7 +549,7 @@ var DrawingArea = new Lang.Class({ }, _startTransforming: function(stageX, stageY, controlPressed, duplicate) { - let [success, startX, startY] = this.transform_stage_point(stageX, stageY); + let [success, startX, startY] = this._transformStagePoint(stageX, stageY); if (!success) return; @@ -549,7 +599,7 @@ var DrawingArea = new Lang.Class({ return; let coords = event.get_coords(); - let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); + let [s, x, y] = this._transformStagePoint(coords[0], coords[1]); if (!s) return; let controlPressed = event.has_control_modifier(); @@ -605,8 +655,7 @@ var DrawingArea = new Lang.Class({ }, _startDrawing: function(stageX, stageY, shiftPressed) { - let [success, startX, startY] = this.transform_stage_point(stageX, stageY); - + let [success, startX, startY] = this._transformStagePoint(stageX, stageY); if (!success) return; @@ -665,9 +714,10 @@ var DrawingArea = new Lang.Class({ return; let coords = event.get_coords(); - let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); + let [s, x, y] = this._transformStagePoint(coords[0], coords[1]); if (!s) return; + let controlPressed = event.has_control_modifier(); this._updateDrawing(x, y, controlPressed); @@ -682,7 +732,7 @@ var DrawingArea = new Lang.Class({ if (!success) return GLib.SOURCE_CONTINUE; - let [s, x, y] = this.transform_stage_point(coords.x, coords.y); + let [s, x, y] = this._transformStagePoint(coords.x, coords.y); if (!s) return GLib.SOURCE_CONTINUE; @@ -703,7 +753,8 @@ var DrawingArea = new Lang.Class({ this.currentElement.updateDrawing(x, y, controlPressed); - this._redisplay(); + //this._redisplay(); + this.foreLayer.queue_repaint(); this.updatePointerCursor(controlPressed); }, @@ -756,7 +807,7 @@ var DrawingArea = new Lang.Class({ // Do not hide and do not set opacity to 0 because ibusCandidatePopup need a mapped text entry to init correctly its position. this.textEntry = new St.Entry({ opacity: 1, x: stageX + x, y: stageY + y }); - this.get_parent().insert_child_below(this.textEntry, this); + this.insert_child_below(this.textEntry, null); this.textEntry.grab_key_focus(); this.updateActionMode(); this.updatePointerCursor(); @@ -766,7 +817,7 @@ var DrawingArea = new Lang.Class({ if (ibusCandidatePopup) { this.ibusHandler = ibusCandidatePopup.connect('notify::visible', () => { if (ibusCandidatePopup.visible) { - this.get_parent().set_child_above_sibling(this.textEntry, this); + this.set_child_above_sibling(this.textEntry, null); this.textEntry.opacity = 255; } }); @@ -952,7 +1003,7 @@ var DrawingArea = new Lang.Class({ toggleBackground: function() { this.hasBackground = !this.hasBackground; - this.get_parent().set_background_color(this.hasBackground ? this.areaBackgroundColor : null); + this.set_background_color(this.hasBackground ? this.areaBackgroundColor : null); }, toggleGrid: function() { @@ -963,13 +1014,13 @@ var DrawingArea = new Lang.Class({ toggleSquareArea: function() { this.isSquareArea = !this.isSquareArea; if (this.isSquareArea) { - this.set_position((this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2); - this.set_size(this.squareAreaSize, this.squareAreaSize); - this.add_style_class_name('draw-on-your-screen-square-area'); + this.layerContainer.set_position((this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2); + this.layerContainer.set_size(this.squareAreaSize, this.squareAreaSize); + this.layerContainer.add_style_class_name('draw-on-your-screen-square-area'); } else { - this.set_position(0, 0); - this.set_size(this.monitor.width, this.monitor.height); - this.remove_style_class_name('draw-on-your-screen-square-area'); + this.layerContainer.set_position(0, 0); + this.layerContainer.set_size(this.monitor.width, this.monitor.height); + this.layerContainer.remove_style_class_name('draw-on-your-screen-square-area'); } }, @@ -1219,7 +1270,7 @@ var DrawingArea = new Lang.Class({ this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); this.keyboardPopupMenuHandler = 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.reactive && this.hasBackground ? this.areaBackgroundColor : null); + this.set_background_color(this.reactive && this.hasBackground ? this.areaBackgroundColor : null); }, leaveDrawingMode: function(save, erase) { @@ -1246,7 +1297,7 @@ var DrawingArea = new Lang.Class({ this.erase(); this.closeMenu(); - this.get_parent().set_background_color(null); + this.set_background_color(null); Files.Images.reset(); if (save) this.savePersistent(); @@ -1304,8 +1355,7 @@ var DrawingArea = new Lang.Class({ content += "\n"; if (Files.saveSvg(content)) { - // pass the parent (bgContainer) to Flashspot because coords of this are relative - let flashspot = new Screenshot.Flashspot(this.get_parent()); + let flashspot = new Screenshot.Flashspot(this); flashspot.fire(); if (global.play_theme_sound) { global.play_theme_sound(0, 'screen-capture', "Save as SVG", null); diff --git a/extension.js b/extension.js index a6d2744..638cf12 100644 --- a/extension.js +++ b/extension.js @@ -131,9 +131,9 @@ const AreaManager = new Lang.Class({ onDesktopSettingChanged: function() { if (this.onDesktop) - this.areas.forEach(area => area.get_parent().show()); + this.areas.forEach(area => area.show()); else - this.areas.forEach(area => area.get_parent().hide()); + this.areas.forEach(area => area.hide()); }, onPersistentOverRestartsSettingChanged: function() { @@ -167,19 +167,15 @@ const AreaManager = new Lang.Class({ for (let i = 0; i < this.monitors.length; i++) { let monitor = this.monitors[i]; - let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i }); let helper = new Helper.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor); let loadPersistent = i == Main.layoutManager.primaryIndex && this.persistentOverRestarts; let area = new Area.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, loadPersistent); - container.add_child(area); - container.add_child(helper); - Main.layoutManager._backgroundGroup.insert_child_above(container, Main.layoutManager._bgManagers[i].backgroundActor); + Main.layoutManager._backgroundGroup.insert_child_above(area, Main.layoutManager._bgManagers[i].backgroundActor); if (!this.onDesktop) - container.hide(); + area.hide(); - container.set_position(monitor.x, monitor.y); - container.set_size(monitor.width, monitor.height); + area.set_position(monitor.x, monitor.y); 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)); @@ -333,25 +329,24 @@ const AreaManager = new Lang.Class({ } }, - toggleContainer: function() { + toggleArea: function() { if (!this.activeArea) return; - let activeContainer = this.activeArea.get_parent(); let activeIndex = this.areas.indexOf(this.activeArea); - if (activeContainer.get_parent() == Main.uiGroup) { + if (this.activeArea.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); + Main.uiGroup.remove_actor(this.activeArea); + Main.layoutManager._backgroundGroup.insert_child_above(this.activeArea, Main.layoutManager._bgManagers[activeIndex].backgroundActor); if (!this.onDesktop) - activeContainer.hide(); + this.activeArea.hide(); } else { - Main.layoutManager._backgroundGroup.remove_actor(activeContainer); - Main.uiGroup.add_child(activeContainer); + Main.layoutManager._backgroundGroup.remove_actor(this.activeArea); + Main.uiGroup.add_child(this.activeArea); // 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); + Main.uiGroup.set_child_above_sibling(Main.layoutManager.keyboardBox, this.activeArea); } }, @@ -397,15 +392,15 @@ const AreaManager = new Lang.Class({ if (Main._findModal(this.activeArea) != -1) this.toggleModal(); - this.toggleContainer(); + this.toggleArea(); 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(); + this.toggleArea(); if (!this.toggleModal()) { - this.toggleContainer(); + this.toggleArea(); this.activeArea = null; return; } @@ -517,9 +512,7 @@ const AreaManager = new Lang.Class({ area.disconnect(area.leaveDrawingHandler); area.disconnect(area.updateActionModeHandler); area.disconnect(area.showOsdHandler); - let container = area.get_parent(); - container.get_parent().remove_actor(container); - container.destroy(); + area.destroy(); } this.areas = []; },