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.
This commit is contained in:
abakkk 2020-10-09 11:51:01 +02:00
parent ed167f8d1b
commit 58677dc175
2 changed files with 143 additions and 100 deletions

202
area.js
View File

@ -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</svg>";
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);

View File

@ -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 = [];
},