screenshot-ui: Add area selection

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1954>
This commit is contained in:
Ivan Molodetskikh 2021-08-16 14:34:49 +03:00 committed by Marge Bot
parent e12689108a
commit f3d59912ec
2 changed files with 712 additions and 10 deletions

View File

@ -70,6 +70,28 @@
}
}
.screenshot-ui-area-indicator-shade {
background-color: rgba(0, 0, 0, .3);
}
.screenshot-ui-area-selector {
.screenshot-ui-area-indicator-shade {
background-color: rgba(0, 0, 0, .5);
}
.screenshot-ui-area-indicator-selection {
border: 2px white;
}
}
.screenshot-ui-area-selector-handle {
border-radius: 99px;
background-color: white;
box-shadow: 0 1px 3px 2px rgba(0, 0, 0, 0.2);
width: 24px;
height: 24px;
}
.screenshot-ui-screen-selector {
transition-duration: 200ms;
background-color: rgba(0, 0, 0, .5);

View File

@ -44,6 +44,594 @@ class IconLabelButton extends St.Button {
}
});
var UIAreaIndicator = GObject.registerClass(
class UIAreaIndicator extends St.Widget {
_init(params) {
super._init(params);
this._topRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
this._topRect.add_constraint(new Clutter.BindConstraint({
source: this,
coordinate: Clutter.BindCoordinate.WIDTH,
}));
this._topRect.add_constraint(new Clutter.SnapConstraint({
source: this,
from_edge: Clutter.SnapEdge.TOP,
to_edge: Clutter.SnapEdge.TOP,
}));
this._topRect.add_constraint(new Clutter.SnapConstraint({
source: this,
from_edge: Clutter.SnapEdge.LEFT,
to_edge: Clutter.SnapEdge.LEFT,
}));
this.add_child(this._topRect);
this._bottomRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
this._bottomRect.add_constraint(new Clutter.BindConstraint({
source: this,
coordinate: Clutter.BindCoordinate.WIDTH,
}));
this._bottomRect.add_constraint(new Clutter.SnapConstraint({
source: this,
from_edge: Clutter.SnapEdge.BOTTOM,
to_edge: Clutter.SnapEdge.BOTTOM,
}));
this._bottomRect.add_constraint(new Clutter.SnapConstraint({
source: this,
from_edge: Clutter.SnapEdge.LEFT,
to_edge: Clutter.SnapEdge.LEFT,
}));
this.add_child(this._bottomRect);
this._leftRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
this._leftRect.add_constraint(new Clutter.SnapConstraint({
source: this,
from_edge: Clutter.SnapEdge.LEFT,
to_edge: Clutter.SnapEdge.LEFT,
}));
this._leftRect.add_constraint(new Clutter.SnapConstraint({
source: this._topRect,
from_edge: Clutter.SnapEdge.TOP,
to_edge: Clutter.SnapEdge.BOTTOM,
}));
this._leftRect.add_constraint(new Clutter.SnapConstraint({
source: this._bottomRect,
from_edge: Clutter.SnapEdge.BOTTOM,
to_edge: Clutter.SnapEdge.TOP,
}));
this.add_child(this._leftRect);
this._rightRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
this._rightRect.add_constraint(new Clutter.SnapConstraint({
source: this,
from_edge: Clutter.SnapEdge.RIGHT,
to_edge: Clutter.SnapEdge.RIGHT,
}));
this._rightRect.add_constraint(new Clutter.SnapConstraint({
source: this._topRect,
from_edge: Clutter.SnapEdge.TOP,
to_edge: Clutter.SnapEdge.BOTTOM,
}));
this._rightRect.add_constraint(new Clutter.SnapConstraint({
source: this._bottomRect,
from_edge: Clutter.SnapEdge.BOTTOM,
to_edge: Clutter.SnapEdge.TOP,
}));
this.add_child(this._rightRect);
this._selectionRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-selection' });
this.add_child(this._selectionRect);
this._topRect.add_constraint(new Clutter.SnapConstraint({
source: this._selectionRect,
from_edge: Clutter.SnapEdge.BOTTOM,
to_edge: Clutter.SnapEdge.TOP,
}));
this._bottomRect.add_constraint(new Clutter.SnapConstraint({
source: this._selectionRect,
from_edge: Clutter.SnapEdge.TOP,
to_edge: Clutter.SnapEdge.BOTTOM,
}));
this._leftRect.add_constraint(new Clutter.SnapConstraint({
source: this._selectionRect,
from_edge: Clutter.SnapEdge.RIGHT,
to_edge: Clutter.SnapEdge.LEFT,
}));
this._rightRect.add_constraint(new Clutter.SnapConstraint({
source: this._selectionRect,
from_edge: Clutter.SnapEdge.LEFT,
to_edge: Clutter.SnapEdge.RIGHT,
}));
}
setSelectionRect(x, y, width, height) {
this._selectionRect.set_position(x, y);
this._selectionRect.set_size(width, height);
}
});
var UIAreaSelector = GObject.registerClass({
Signals: { 'drag-started': {}, 'drag-ended': {} },
}, class UIAreaSelector extends St.Widget {
_init(params) {
super._init(params);
// During a drag, this can be Clutter.BUTTON_PRIMARY,
// Clutter.BUTTON_SECONDARY or the string "touch" to identify the source
// of the drag operation.
this._dragButton = 0;
this._dragDevice = null;
this._dragSequence = null;
this._areaIndicator = new UIAreaIndicator();
this._areaIndicator.add_constraint(new Clutter.BindConstraint({
source: this,
coordinate: Clutter.BindCoordinate.ALL,
}));
this.add_child(this._areaIndicator);
this._topLeftHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
this.add_child(this._topLeftHandle);
this._topRightHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
this.add_child(this._topRightHandle);
this._bottomLeftHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
this.add_child(this._bottomLeftHandle);
this._bottomRightHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
this.add_child(this._bottomRightHandle);
// This will be updated before the first drawn frame.
this._handleSize = 0;
this._topLeftHandle.connect('style-changed', widget => {
this._handleSize = widget.get_theme_node().get_width();
this._updateSelectionRect();
});
this.connect('notify::mapped', () => {
if (this.mapped) {
const [x, y] = global.get_pointer();
this._updateCursor(x, y);
}
});
// Initialize area to out of bounds so reset() below resets it.
this._startX = -1;
this._startY = 0;
this._lastX = 0;
this._lastY = 0;
this.reset();
}
reset() {
this.stopDrag();
global.display.set_cursor(Meta.Cursor.DEFAULT);
// Preserve area selection if possible. If the area goes out of bounds,
// the monitors might have changed, so reset the area.
const [x, y, w, h] = this.getGeometry();
if (x < 0 || y < 0 || x + w > this.width || y + h > this.height) {
// Initialize area to out of bounds so if there's no monitor,
// the area will be reset once a monitor does appear.
this._startX = -1;
this._startY = 0;
this._lastX = 0;
this._lastY = 0;
// This can happen when running headless without any monitors.
if (Main.layoutManager.primaryIndex !== -1) {
const monitor =
Main.layoutManager.monitors[Main.layoutManager.primaryIndex];
this._startX = monitor.x + Math.floor(monitor.width * 3 / 8);
this._startY = monitor.y + Math.floor(monitor.height * 3 / 8);
this._lastX = monitor.x + Math.floor(monitor.width * 5 / 8) - 1;
this._lastY = monitor.y + Math.floor(monitor.height * 5 / 8) - 1;
}
this._updateSelectionRect();
}
}
getGeometry() {
const leftX = Math.min(this._startX, this._lastX);
const topY = Math.min(this._startY, this._lastY);
const rightX = Math.max(this._startX, this._lastX);
const bottomY = Math.max(this._startY, this._lastY);
return [leftX, topY, rightX - leftX + 1, bottomY - topY + 1];
}
_updateSelectionRect() {
const [x, y, w, h] = this.getGeometry();
this._areaIndicator.setSelectionRect(x, y, w, h);
const offset = this._handleSize / 2;
this._topLeftHandle.set_position(x - offset, y - offset);
this._topRightHandle.set_position(x + w - 1 - offset, y - offset);
this._bottomLeftHandle.set_position(x - offset, y + h - 1 - offset);
this._bottomRightHandle.set_position(x + w - 1 - offset, y + h - 1 - offset);
}
_computeCursorType(cursorX, cursorY) {
const [leftX, topY, width, height] = this.getGeometry();
const [rightX, bottomY] = [leftX + width - 1, topY + height - 1];
const [x, y] = [cursorX, cursorY];
// Check if the cursor overlaps the handles first.
const limit = (this._handleSize / 2) ** 2;
if ((leftX - x) ** 2 + (topY - y) ** 2 <= limit)
return Meta.Cursor.NW_RESIZE;
else if ((rightX - x) ** 2 + (topY - y) ** 2 <= limit)
return Meta.Cursor.NE_RESIZE;
else if ((leftX - x) ** 2 + (bottomY - y) ** 2 <= limit)
return Meta.Cursor.SW_RESIZE;
else if ((rightX - x) ** 2 + (bottomY - y) ** 2 <= limit)
return Meta.Cursor.SE_RESIZE;
// Now check the rest of the rectangle.
const threshold =
10 * St.ThemeContext.get_for_stage(global.stage).scaleFactor;
if (leftX - x >= 0 && leftX - x <= threshold) {
if (topY - y >= 0 && topY - y <= threshold)
return Meta.Cursor.NW_RESIZE;
else if (y - bottomY >= 0 && y - bottomY <= threshold)
return Meta.Cursor.SW_RESIZE;
else if (topY - y < 0 && y - bottomY < 0)
return Meta.Cursor.WEST_RESIZE;
} else if (x - rightX >= 0 && x - rightX <= threshold) {
if (topY - y >= 0 && topY - y <= threshold)
return Meta.Cursor.NE_RESIZE;
else if (y - bottomY >= 0 && y - bottomY <= threshold)
return Meta.Cursor.SE_RESIZE;
else if (topY - y < 0 && y - bottomY < 0)
return Meta.Cursor.EAST_RESIZE;
} else if (leftX - x < 0 && x - rightX < 0) {
if (topY - y >= 0 && topY - y <= threshold)
return Meta.Cursor.NORTH_RESIZE;
else if (y - bottomY >= 0 && y - bottomY <= threshold)
return Meta.Cursor.SOUTH_RESIZE;
else if (topY - y < 0 && y - bottomY < 0)
return Meta.Cursor.MOVE_OR_RESIZE_WINDOW;
}
return Meta.Cursor.CROSSHAIR;
}
stopDrag() {
if (!this._dragButton)
return;
if (this._dragSequence)
this._dragDevice.sequence_ungrab(this._dragSequence);
else
this._dragDevice.ungrab();
this._dragButton = 0;
this._dragDevice = null;
this._dragSequence = null;
if (this._dragCursor === Meta.Cursor.CROSSHAIR &&
this._lastX === this._startX && this._lastY === this._startY) {
// The user clicked without dragging. Make up a larger selection
// to reduce confusion.
const offset =
20 * St.ThemeContext.get_for_stage(global.stage).scaleFactor;
this._startX -= offset;
this._startY -= offset;
this._lastX += offset;
this._lastY += offset;
// Keep the coordinates inside the stage.
if (this._startX < 0) {
this._lastX -= this._startX;
this._startX = 0;
} else if (this._lastX >= this.width) {
this._startX -= this._lastX - this.width + 1;
this._lastX = this.width - 1;
}
if (this._startY < 0) {
this._lastY -= this._startY;
this._startY = 0;
} else if (this._lastY >= this.height) {
this._startY -= this._lastY - this.height + 1;
this._lastY = this.height - 1;
}
this._updateSelectionRect();
}
this.emit('drag-ended');
}
_updateCursor(x, y) {
const cursor = this._computeCursorType(x, y);
global.display.set_cursor(cursor);
}
_onPress(event, button, sequence) {
if (this._dragButton)
return Clutter.EVENT_PROPAGATE;
const cursor = this._computeCursorType(event.x, event.y);
// Clicking outside of the selection, or using the right mouse button,
// or with Ctrl results in dragging a new selection from scratch.
if (cursor === Meta.Cursor.CROSSHAIR ||
button === Clutter.BUTTON_SECONDARY ||
(event.modifier_state & Clutter.ModifierType.CONTROL_MASK)) {
this._dragButton = button;
this._dragCursor = Meta.Cursor.CROSSHAIR;
global.display.set_cursor(Meta.Cursor.CROSSHAIR);
[this._startX, this._startY] = [event.x, event.y];
this._lastX = this._startX = Math.floor(this._startX);
this._lastY = this._startY = Math.floor(this._startY);
this._updateSelectionRect();
} else {
// This is a move or resize operation.
this._dragButton = button;
this._dragCursor = cursor;
this._dragStartX = event.x;
this._dragStartY = event.y;
const [leftX, topY, width, height] = this.getGeometry();
const rightX = leftX + width - 1;
const bottomY = topY + height - 1;
// For moving, start X and Y are the top left corner, while
// last X and Y are the bottom right corner.
if (cursor === Meta.Cursor.MOVE_OR_RESIZE_WINDOW) {
this._startX = leftX;
this._startY = topY;
this._lastX = rightX;
this._lastY = bottomY;
}
// Start X and Y are set to the stationary sides, while last X
// and Y are set to the moving sides.
if (cursor === Meta.Cursor.NW_RESIZE ||
cursor === Meta.Cursor.WEST_RESIZE ||
cursor === Meta.Cursor.SW_RESIZE) {
this._startX = rightX;
this._lastX = leftX;
}
if (cursor === Meta.Cursor.NE_RESIZE ||
cursor === Meta.Cursor.EAST_RESIZE ||
cursor === Meta.Cursor.SE_RESIZE) {
this._startX = leftX;
this._lastX = rightX;
}
if (cursor === Meta.Cursor.NW_RESIZE ||
cursor === Meta.Cursor.NORTH_RESIZE ||
cursor === Meta.Cursor.NE_RESIZE) {
this._startY = bottomY;
this._lastY = topY;
}
if (cursor === Meta.Cursor.SW_RESIZE ||
cursor === Meta.Cursor.SOUTH_RESIZE ||
cursor === Meta.Cursor.SE_RESIZE) {
this._startY = topY;
this._lastY = bottomY;
}
}
if (this._dragButton) {
const device = event.device;
if (sequence)
device.sequence_grab(sequence, this);
else
device.grab(this);
this._dragDevice = device;
this._dragSequence = sequence;
this.emit('drag-started');
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
}
_onRelease(event, button, sequence) {
if (this._dragButton !== button ||
this._dragSequence?.get_slot() !== sequence?.get_slot())
return Clutter.EVENT_PROPAGATE;
this.stopDrag();
// We might have finished creating a new selection, so we need to
// update the cursor.
this._updateCursor(event.x, event.y);
return Clutter.EVENT_STOP;
}
_onMotion(event, sequence) {
if (!this._dragButton) {
this._updateCursor(event.x, event.y);
return Clutter.EVENT_PROPAGATE;
}
if (sequence?.get_slot() !== this._dragSequence?.get_slot())
return Clutter.EVENT_PROPAGATE;
if (this._dragCursor === Meta.Cursor.CROSSHAIR) {
[this._lastX, this._lastY] = [event.x, event.y];
this._lastX = Math.floor(this._lastX);
this._lastY = Math.floor(this._lastY);
} else {
let dx = Math.round(event.x - this._dragStartX);
let dy = Math.round(event.y - this._dragStartY);
if (this._dragCursor === Meta.Cursor.MOVE_OR_RESIZE_WINDOW) {
const [,, selectionWidth, selectionHeight] = this.getGeometry();
let newStartX = this._startX + dx;
let newStartY = this._startY + dy;
let newLastX = this._lastX + dx;
let newLastY = this._lastY + dy;
let overshootX = 0;
let overshootY = 0;
// Keep the size intact if we bumped into the stage edge.
if (newStartX < 0) {
overshootX = 0 - newStartX;
newStartX = 0;
newLastX = newStartX + (selectionWidth - 1);
} else if (newLastX > this.width - 1) {
overshootX = (this.width - 1) - newLastX;
newLastX = this.width - 1;
newStartX = newLastX - (selectionWidth - 1);
}
if (newStartY < 0) {
overshootY = 0 - newStartY;
newStartY = 0;
newLastY = newStartY + (selectionHeight - 1);
} else if (newLastY > this.height - 1) {
overshootY = (this.height - 1) - newLastY;
newLastY = this.height - 1;
newStartY = newLastY - (selectionHeight - 1);
}
// Add the overshoot to the delta to create a "rubberbanding"
// behavior of the pointer when dragging.
dx += overshootX;
dy += overshootY;
this._startX = newStartX;
this._startY = newStartY;
this._lastX = newLastX;
this._lastY = newLastY;
} else {
if (this._dragCursor === Meta.Cursor.WEST_RESIZE ||
this._dragCursor === Meta.Cursor.EAST_RESIZE)
dy = 0;
if (this._dragCursor === Meta.Cursor.NORTH_RESIZE ||
this._dragCursor === Meta.Cursor.SOUTH_RESIZE)
dx = 0;
// Make sure last X and Y are clamped between 0 and size - 1,
// while always preserving the cursor dragging position relative
// to the selection rectangle.
this._lastX += dx;
if (this._lastX >= this.width) {
dx -= this._lastX - this.width + 1;
this._lastX = this.width - 1;
} else if (this._lastX < 0) {
dx -= this._lastX;
this._lastX = 0;
}
this._lastY += dy;
if (this._lastY >= this.height) {
dy -= this._lastY - this.height + 1;
this._lastY = this.height - 1;
} else if (this._lastY < 0) {
dy -= this._lastY;
this._lastY = 0;
}
// If we drag the handle past a selection side, update which
// handles are which.
if (this._lastX > this._startX) {
if (this._dragCursor === Meta.Cursor.NW_RESIZE)
this._dragCursor = Meta.Cursor.NE_RESIZE;
else if (this._dragCursor === Meta.Cursor.SW_RESIZE)
this._dragCursor = Meta.Cursor.SE_RESIZE;
else if (this._dragCursor === Meta.Cursor.WEST_RESIZE)
this._dragCursor = Meta.Cursor.EAST_RESIZE;
} else {
// eslint-disable-next-line no-lonely-if
if (this._dragCursor === Meta.Cursor.NE_RESIZE)
this._dragCursor = Meta.Cursor.NW_RESIZE;
else if (this._dragCursor === Meta.Cursor.SE_RESIZE)
this._dragCursor = Meta.Cursor.SW_RESIZE;
else if (this._dragCursor === Meta.Cursor.EAST_RESIZE)
this._dragCursor = Meta.Cursor.WEST_RESIZE;
}
if (this._lastY > this._startY) {
if (this._dragCursor === Meta.Cursor.NW_RESIZE)
this._dragCursor = Meta.Cursor.SW_RESIZE;
else if (this._dragCursor === Meta.Cursor.NE_RESIZE)
this._dragCursor = Meta.Cursor.SE_RESIZE;
else if (this._dragCursor === Meta.Cursor.NORTH_RESIZE)
this._dragCursor = Meta.Cursor.SOUTH_RESIZE;
} else {
// eslint-disable-next-line no-lonely-if
if (this._dragCursor === Meta.Cursor.SW_RESIZE)
this._dragCursor = Meta.Cursor.NW_RESIZE;
else if (this._dragCursor === Meta.Cursor.SE_RESIZE)
this._dragCursor = Meta.Cursor.NE_RESIZE;
else if (this._dragCursor === Meta.Cursor.SOUTH_RESIZE)
this._dragCursor = Meta.Cursor.NORTH_RESIZE;
}
global.display.set_cursor(this._dragCursor);
}
this._dragStartX += dx;
this._dragStartY += dy;
}
this._updateSelectionRect();
return Clutter.EVENT_STOP;
}
vfunc_button_press_event(event) {
if (event.button === Clutter.BUTTON_PRIMARY ||
event.button === Clutter.BUTTON_SECONDARY)
return this._onPress(event, event.button, null);
return Clutter.EVENT_PROPAGATE;
}
vfunc_button_release_event(event) {
if (event.button === Clutter.BUTTON_PRIMARY ||
event.button === Clutter.BUTTON_SECONDARY)
return this._onRelease(event, event.button, null);
return Clutter.EVENT_PROPAGATE;
}
vfunc_motion_event(event) {
return this._onMotion(event, null);
}
vfunc_touch_event(event) {
if (event.type === Clutter.EventType.TOUCH_BEGIN)
return this._onPress(event, 'touch', event.sequence);
else if (event.type === Clutter.EventType.TOUCH_END)
return this._onRelease(event, 'touch', event.sequence);
else if (event.type === Clutter.EventType.TOUCH_UPDATE)
return this._onMotion(event, event.sequence);
return Clutter.EVENT_PROPAGATE;
}
vfunc_leave_event(event) {
// If we're dragging and go over the panel we still get a leave event
// for some reason, even though we have a grab. We don't want to switch
// the cursor when we're dragging.
if (!this._dragButton)
global.display.set_cursor(Meta.Cursor.DEFAULT);
return super.vfunc_leave_event(event);
}
});
var ScreenshotUI = GObject.registerClass(
class ScreenshotUI extends St.Widget {
_init() {
@ -82,6 +670,14 @@ class ScreenshotUI extends St.Widget {
actionMode: Shell.ActionMode.POPUP,
});
this._areaSelector = new UIAreaSelector({
style_class: 'screenshot-ui-area-selector',
x_expand: true,
y_expand: true,
reactive: true,
});
this.add_child(this._areaSelector);
this._primaryMonitorBin = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this._primaryMonitorBin.add_constraint(
new Layout.MonitorConstraint({ 'primary': true }));
@ -106,6 +702,31 @@ class ScreenshotUI extends St.Widget {
this._closeButton.connect('clicked', () => this.close());
this._primaryMonitorBin.add_child(this._closeButton);
this._areaSelector.connect('drag-started', () => {
this._panel.ease({
opacity: 100,
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this._closeButton.ease({
opacity: 100,
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
});
this._areaSelector.connect('drag-ended', () => {
this._panel.ease({
opacity: 255,
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this._closeButton.ease({
opacity: 255,
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
});
this._typeButtonContainer = new St.Widget({
style_class: 'screenshot-ui-type-button-container',
layout_manager: new Clutter.BoxLayout({
@ -115,11 +736,20 @@ class ScreenshotUI extends St.Widget {
});
this._panel.add_child(this._typeButtonContainer);
this._screenButton = new IconLabelButton('video-display-symbolic', _('Screen'), {
this._selectionButton = new IconLabelButton('input-mouse-symbolic', _('Selection'), {
style_class: 'screenshot-ui-type-button',
checked: true,
x_expand: true,
});
this._selectionButton.connect('notify::checked',
this._onSelectionButtonToggled.bind(this));
this._typeButtonContainer.add_child(this._selectionButton);
this._screenButton = new IconLabelButton('video-display-symbolic', _('Screen'), {
style_class: 'screenshot-ui-type-button',
toggle_mode: true,
x_expand: true,
});
this._screenButton.connect('notify::checked',
this._onScreenButtonToggled.bind(this));
this._typeButtonContainer.add_child(this._screenButton);
@ -271,6 +901,8 @@ class ScreenshotUI extends St.Widget {
this._stageScreenshotContainer.hide();
this._stageScreenshot.set_content(null);
this._areaSelector.reset();
}
close(instantly = false) {
@ -290,9 +922,40 @@ class ScreenshotUI extends St.Widget {
});
}
_onSelectionButtonToggled() {
if (this._selectionButton.checked) {
this._selectionButton.toggle_mode = false;
this._screenButton.checked = false;
this._areaSelector.show();
this._areaSelector.remove_all_transitions();
this._areaSelector.reactive = true;
this._areaSelector.ease({
opacity: 255,
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
} else {
this._selectionButton.toggle_mode = true;
this._areaSelector.stopDrag();
global.display.set_cursor(Meta.Cursor.DEFAULT);
this._areaSelector.remove_all_transitions();
this._areaSelector.reactive = false;
this._areaSelector.ease({
opacity: 0,
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._areaSelector.hide(),
});
}
}
_onScreenButtonToggled() {
if (this._screenButton.checked) {
this._screenButton.toggle_mode = false;
this._selectionButton.checked = false;
for (const selector of this._screenSelectors) {
selector.show();
@ -318,11 +981,35 @@ class ScreenshotUI extends St.Widget {
}
}
_getSelectedGeometry() {
let x, y, w, h;
if (this._selectionButton.checked) {
[x, y, w, h] = this._areaSelector.getGeometry();
} else if (this._screenButton.checked) {
const index =
this._screenSelectors.findIndex(screen => screen.checked);
const monitor = Main.layoutManager.monitors[index];
x = monitor.x;
y = monitor.y;
w = monitor.width;
h = monitor.height;
}
x *= this._scale;
y *= this._scale;
w *= this._scale;
h *= this._scale;
return [x, y, w, h];
}
_onCaptureButtonClicked() {
global.display.get_sound_player().play_from_theme(
'screen-capture', _('Screenshot taken'), null);
if (this._screenButton.checked) {
if (this._selectionButton.checked || this._screenButton.checked) {
const content = this._stageScreenshot.get_content();
if (!content) {
// Failed to capture the screenshot for some reason.
@ -333,14 +1020,7 @@ class ScreenshotUI extends St.Widget {
const texture = content.get_texture();
const stream = Gio.MemoryOutputStream.new_resizable();
const index =
this._screenSelectors.findIndex(screen => screen.checked);
const monitor = Main.layoutManager.monitors[index];
const x = monitor.x * this._scale;
const y = monitor.y * this._scale;
const w = monitor.width * this._scale;
const h = monitor.height * this._scale;
const [x, y, w, h] = this._getSelectedGeometry();
Shell.Screenshot.composite_to_stream(
texture,