workspaceThumbnail: Make it horizontal

Allocate workspace thumbnails horizontally. This requires introducing code
to handle the RTL direction. Do a small rewrite of the DnD hover method to
be simultaneously simpler and easier to follow, and work correctly on RTL.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1593>
This commit is contained in:
Georges Basile Stavracas Neto 2021-01-25 20:52:17 -03:00 committed by Marge Bot
parent 99d1529e8c
commit 3ad7b85e25

View File

@ -627,9 +627,11 @@ var ThumbnailsBox = GObject.registerClass({
},
}, class ThumbnailsBox extends St.Widget {
_init(scrollAdjustment) {
super._init({ reactive: true,
style_class: 'workspace-thumbnails',
request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT });
super._init({
style_class: 'workspace-thumbnails',
reactive: true,
x_align: Clutter.ActorAlign.CENTER,
});
this._delegate = this;
@ -723,11 +725,11 @@ var ThumbnailsBox = GObject.registerClass({
}
_activateThumbnailAtPoint(stageX, stageY, time) {
let [r_, x_, y] = this.transform_stage_point(stageX, stageY);
const [r_, x] = this.transform_stage_point(stageX, stageY);
let thumbnail = this._thumbnails.find(t => {
let [, h] = t.get_transformed_size();
return y >= t.y && y <= t.y + h;
const thumbnail = this._thumbnails.find(t => {
const [w] = t.get_transformed_size();
return x >= t.x && x <= t.x + w;
});
if (thumbnail)
thumbnail.activate(time);
@ -792,6 +794,57 @@ var ThumbnailsBox = GObject.registerClass({
this.queue_relayout();
}
_getPlaceholderTarget(index, spacing, rtl) {
const workspace = this._thumbnails[index];
let targetX1;
let targetX2;
if (rtl) {
const [r_, w] = workspace.get_transformed_size();
const baseX = workspace.x + w;
targetX1 = baseX - WORKSPACE_CUT_SIZE;
targetX2 = baseX + spacing + WORKSPACE_CUT_SIZE;
} else {
targetX1 = workspace.x - spacing - WORKSPACE_CUT_SIZE;
targetX2 = workspace.x + WORKSPACE_CUT_SIZE;
}
if (index === 0) {
if (rtl)
targetX2 -= spacing + WORKSPACE_CUT_SIZE;
else
targetX1 += spacing + WORKSPACE_CUT_SIZE;
}
if (index === this._dropPlaceholderPos) {
const placeholderWidth = this._dropPlaceholder.get_width() + spacing;
if (rtl)
targetX2 += placeholderWidth;
else
targetX1 -= placeholderWidth;
}
return [targetX1, targetX2];
}
_withinWorkspace(x, index, rtl) {
const length = this._thumbnails.length;
const workspace = this._thumbnails[index];
let workspaceX1 = workspace.x + WORKSPACE_CUT_SIZE;
let workspaceX2 = workspace.x + workspace.width - WORKSPACE_CUT_SIZE;
if (index === length - 1) {
if (rtl)
workspaceX1 -= WORKSPACE_CUT_SIZE;
else
workspaceX2 += WORKSPACE_CUT_SIZE;
}
return x > workspaceX1 && x <= workspaceX2;
}
// Draggable target interface
handleDragOver(source, actor, x, y, time) {
if (!source.metaWindow &&
@ -800,41 +853,30 @@ var ThumbnailsBox = GObject.registerClass({
source != Main.xdndHandler)
return DND.DragMotionResult.CONTINUE;
const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
let canCreateWorkspaces = Meta.prefs_get_dynamic_workspaces();
let spacing = this.get_theme_node().get_length('spacing');
this._dropWorkspace = -1;
let placeholderPos = -1;
let targetBase;
if (this._dropPlaceholderPos == 0)
targetBase = this._dropPlaceholder.y;
else
targetBase = this._thumbnails[0].y;
let targetTop = targetBase - spacing - WORKSPACE_CUT_SIZE;
let length = this._thumbnails.length;
for (let i = 0; i < length; i++) {
// Allow the reorder target to have a 10px "cut" into
// each side of the thumbnail, to make dragging onto the
// placeholder easier
let [, h] = this._thumbnails[i].get_transformed_size();
let targetBottom = targetBase + WORKSPACE_CUT_SIZE;
let nextTargetBase = targetBase + h + spacing;
let nextTargetTop = nextTargetBase - spacing - (i == length - 1 ? 0 : WORKSPACE_CUT_SIZE);
const index = rtl ? length - i - 1 : i;
// Expand the target to include the placeholder, if it exists.
if (i == this._dropPlaceholderPos)
targetBottom += this._dropPlaceholder.get_height();
if (canCreateWorkspaces && source !== Main.xdndHandler) {
const [targetStart, targetEnd] =
this._getPlaceholderTarget(index, spacing, rtl);
if (y > targetTop && y <= targetBottom && source != Main.xdndHandler && canCreateWorkspaces) {
placeholderPos = i;
break;
} else if (y > targetBottom && y <= nextTargetTop) {
this._dropWorkspace = i;
break;
if (x > targetStart && x <= targetEnd) {
placeholderPos = index;
break;
}
}
targetBase = nextTargetBase;
targetTop = nextTargetTop;
if (this._withinWorkspace(x, index, rtl)) {
this._dropWorkspace = index;
break;
}
}
if (this._dropPlaceholderPos != placeholderPos) {
@ -1156,8 +1198,27 @@ var ThumbnailsBox = GObject.registerClass({
this._stateUpdateQueued = true;
}
vfunc_get_preferred_height(_forWidth) {
// Note that for getPreferredWidth/Height we cheat a bit and skip propagating
vfunc_get_preferred_height(forWidth) {
let workspaceManager = global.workspace_manager;
let themeNode = this.get_theme_node();
forWidth = themeNode.adjust_for_width(forWidth);
let spacing = themeNode.get_length('spacing');
let nWorkspaces = workspaceManager.n_workspaces;
let totalSpacing = (nWorkspaces - 1) * spacing;
const avail = forWidth - totalSpacing;
let scale = (avail / nWorkspaces) / this._porthole.width;
scale = Math.min(scale, MAX_THUMBNAIL_SCALE);
const height = Math.round(this._porthole.height * scale);
return themeNode.adjust_preferred_height(height, height);
}
vfunc_get_preferred_width(_forHeight) {
// Note that for getPreferredHeight/Width we cheat a bit and skip propagating
// the size request to our children because we know how big they are and know
// that the actors aren't depending on the virtual functions being called.
let workspaceManager = global.workspace_manager;
@ -1167,29 +1228,9 @@ var ThumbnailsBox = GObject.registerClass({
let nWorkspaces = workspaceManager.n_workspaces;
let totalSpacing = (nWorkspaces - 1) * spacing;
let naturalHeight = totalSpacing + nWorkspaces * this._porthole.height * MAX_THUMBNAIL_SCALE;
return themeNode.adjust_preferred_height(totalSpacing, naturalHeight);
}
vfunc_get_preferred_width(forHeight) {
let workspaceManager = global.workspace_manager;
let themeNode = this.get_theme_node();
forHeight = themeNode.adjust_for_height(forHeight);
let spacing = themeNode.get_length('spacing');
let nWorkspaces = workspaceManager.n_workspaces;
let totalSpacing = (nWorkspaces - 1) * spacing;
let avail = forHeight - totalSpacing;
let scale = (avail / nWorkspaces) / this._porthole.height;
scale = Math.min(scale, MAX_THUMBNAIL_SCALE);
let width = Math.round(this._porthole.width * scale);
return themeNode.adjust_preferred_width(width, width);
const naturalWidth =
totalSpacing + nWorkspaces * this._porthole.width * MAX_THUMBNAIL_SCALE;
return themeNode.adjust_preferred_width(totalSpacing, naturalWidth);
}
_updatePorthole() {
@ -1216,17 +1257,18 @@ var ThumbnailsBox = GObject.registerClass({
box = themeNode.get_content_box(box);
let portholeWidth = this._porthole.width;
let portholeHeight = this._porthole.height;
let spacing = themeNode.get_length('spacing');
const portholeWidth = this._porthole.width;
const portholeHeight = this._porthole.height;
const spacing = themeNode.get_length('spacing');
// Compute the scale we'll need once everything is updated
let nWorkspaces = workspaceManager.n_workspaces;
let totalSpacing = (nWorkspaces - 1) * spacing;
let avail = (box.y2 - box.y1) - totalSpacing;
const availableWidth = (box.get_width() - totalSpacing) / nWorkspaces;
let newScale = (avail / nWorkspaces) / portholeHeight;
newScale = Math.min(newScale, MAX_THUMBNAIL_SCALE);
const hScale = availableWidth / portholeWidth;
const vScale = box.get_height() / portholeHeight;
const newScale = Math.min(hScale, vScale);
if (newScale != this._targetScale) {
if (this._targetScale > 0) {
@ -1242,24 +1284,19 @@ var ThumbnailsBox = GObject.registerClass({
this._queueUpdateStates();
}
let thumbnailHeight = portholeHeight * this._scale;
let thumbnailWidth = Math.round(portholeWidth * this._scale);
let roundedHScale = thumbnailWidth / portholeWidth;
let slideOffset; // X offset when thumbnail is fully slid offscreen
if (rtl)
slideOffset = -(thumbnailWidth + themeNode.get_padding(St.Side.LEFT));
else
slideOffset = thumbnailWidth + themeNode.get_padding(St.Side.RIGHT);
const ratio = portholeWidth / portholeHeight;
const thumbnailHeight = Math.round(portholeHeight * this._scale);
const thumbnailWidth = Math.round(thumbnailHeight * ratio);
const roundedVScale = thumbnailHeight / portholeHeight;
let indicatorValue = this._scrollAdjustment.value;
let indicatorUpperWs = Math.ceil(indicatorValue);
let indicatorLowerWs = Math.floor(indicatorValue);
let indicatorLowerY1 = 0;
let indicatorLowerY2 = 0;
let indicatorUpperY1 = 0;
let indicatorUpperY2 = 0;
let indicatorLowerX1 = 0;
let indicatorLowerX2 = 0;
let indicatorUpperX1 = 0;
let indicatorUpperX2 = 0;
let indicatorThemeNode = this._indicator.get_theme_node();
let indicatorTopFullBorder = indicatorThemeNode.get_padding(St.Side.TOP) + indicatorThemeNode.get_border_width(St.Side.TOP);
@ -1267,7 +1304,7 @@ var ThumbnailsBox = GObject.registerClass({
let indicatorLeftFullBorder = indicatorThemeNode.get_padding(St.Side.LEFT) + indicatorThemeNode.get_border_width(St.Side.LEFT);
let indicatorRightFullBorder = indicatorThemeNode.get_padding(St.Side.RIGHT) + indicatorThemeNode.get_border_width(St.Side.RIGHT);
let y = box.y1;
let x = box.x1;
if (this._dropPlaceholderPos == -1) {
this._dropPlaceholder.allocate_preferred_size(
@ -1281,82 +1318,84 @@ var ThumbnailsBox = GObject.registerClass({
let childBox = new Clutter.ActorBox();
for (let i = 0; i < this._thumbnails.length; i++) {
let thumbnail = this._thumbnails[i];
const thumbnail = this._thumbnails[i];
if (i > 0)
y += spacing - Math.round(thumbnail.collapse_fraction * spacing);
x += spacing - Math.round(thumbnail.collapse_fraction * spacing);
let x1, x2;
if (rtl) {
x1 = box.x1 + slideOffset * thumbnail.slide_position;
x2 = x1 + thumbnailWidth;
} else {
x1 = box.x2 - thumbnailWidth + slideOffset * thumbnail.slide_position;
x2 = x1 + thumbnailWidth;
}
const y1 = box.y1;
const y2 = y1 + thumbnailHeight;
if (i === this._dropPlaceholderPos) {
const [, placeholderWidth] = this._dropPlaceholder.get_preferred_width(-1);
childBox.y1 = y1;
childBox.y2 = y2;
if (rtl) {
childBox.x2 = box.x2 - Math.round(x);
childBox.x1 = box.x2 - Math.round(x + placeholderWidth);
} else {
childBox.x1 = Math.round(x);
childBox.x2 = Math.round(x + placeholderWidth);
}
if (i == this._dropPlaceholderPos) {
let [, placeholderHeight] = this._dropPlaceholder.get_preferred_height(-1);
childBox.x1 = x1;
childBox.x2 = x2;
childBox.y1 = Math.round(y);
childBox.y2 = Math.round(y + placeholderHeight);
this._dropPlaceholder.allocate(childBox);
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._dropPlaceholder.show();
});
y += placeholderHeight + spacing;
x += placeholderWidth + spacing;
}
// We might end up with thumbnailHeight being something like 99.33
// pixels. To make this work and not end up with a gap at the bottom,
// we need some thumbnails to be 99 pixels and some 100 pixels height;
// We might end up with thumbnailWidth being something like 99.33
// pixels. To make this work and not end up with a gap at the end,
// we need some thumbnails to be 99 pixels and some 100 pixels width;
// we compute an actual scale separately for each thumbnail.
let y1 = Math.round(y);
let y2 = Math.round(y + thumbnailHeight);
let roundedVScale = (y2 - y1) / portholeHeight;
if (i === indicatorUpperWs) {
indicatorUpperY1 = y1;
indicatorUpperY2 = y2;
}
if (i === indicatorLowerWs) {
indicatorLowerY1 = y1;
indicatorLowerY2 = y2;
}
const x1 = Math.round(x);
const x2 = Math.round(x + thumbnailWidth);
const roundedHScale = (x2 - x1) / portholeWidth;
// Allocating a scaled actor is funny - x1/y1 correspond to the origin
// of the actor, but x2/y2 are increased by the *unscaled* size.
childBox.x1 = x1;
childBox.x2 = x1 + portholeWidth;
if (rtl) {
childBox.x2 = box.x2 - x1;
childBox.x1 = box.x2 - (x1 + portholeWidth);
} else {
childBox.x1 = x1;
childBox.x2 = x1 + portholeWidth;
}
childBox.y1 = y1;
childBox.y2 = y1 + portholeHeight;
thumbnail.set_scale(roundedHScale, roundedVScale);
thumbnail.allocate(childBox);
if (i === indicatorUpperWs) {
indicatorUpperX1 = childBox.x1;
indicatorUpperX2 = childBox.x2;
}
if (i === indicatorLowerWs) {
indicatorLowerX1 = childBox.x1;
indicatorLowerX2 = childBox.x2;
}
// We round the collapsing portion so that we don't get thumbnails resizing
// during an animation due to differences in rounded, but leave the uncollapsed
// portion unrounded so that non-animating we end up with the right total
y += thumbnailHeight - Math.round(thumbnailHeight * thumbnail.collapse_fraction);
x += thumbnailWidth - Math.round(thumbnailWidth * thumbnail.collapse_fraction);
}
if (rtl) {
childBox.x1 = box.x1;
childBox.x2 = box.x1 + thumbnailWidth;
} else {
childBox.x1 = box.x2 - thumbnailWidth;
childBox.x2 = box.x2;
}
let indicatorY1 = indicatorLowerY1 +
(indicatorUpperY1 - indicatorLowerY1) * (indicatorValue % 1);
let indicatorY2 = indicatorLowerY2 +
(indicatorUpperY2 - indicatorLowerY2) * (indicatorValue % 1);
childBox.y1 = box.y1;
childBox.y2 = box.y1 + thumbnailHeight;
childBox.x1 -= indicatorLeftFullBorder;
childBox.x2 += indicatorRightFullBorder;
childBox.y1 = indicatorY1 - indicatorTopFullBorder;
childBox.y2 = indicatorY2 + indicatorBottomFullBorder;
const indicatorX1 = indicatorLowerX1 +
(indicatorUpperX1 - indicatorLowerX1) * (indicatorValue % 1);
const indicatorX2 = indicatorLowerX2 +
(indicatorUpperX2 - indicatorLowerX2) * (indicatorValue % 1);
childBox.x1 = indicatorX1 - indicatorLeftFullBorder;
childBox.x2 = indicatorX2 + indicatorRightFullBorder;
childBox.y1 -= indicatorTopFullBorder;
childBox.y2 += indicatorBottomFullBorder;
this._indicator.allocate(childBox);
}
});