diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in
index 49d38d7662892e862a3e03cb5e21bb9bcdd92465..515e15dbbf891f7618de8eb0fd3b4e1efc1b0904 100644
--- a/data/org.gnome.shell.gschema.xml.in
+++ b/data/org.gnome.shell.gschema.xml.in
@@ -302,4 +302,42 @@
+
+
+
+
+
+
+
+
+
+ 'AUTOHIDE'
+ Dock display mode.
+ Autohide, fixed, or overview only options.
+
+
+ 48
+ Dash icon size when fixed.
+ When fixed-icon-size is true this size will be used by the dash.
+
+
+ false
+ Fixed icon size
+ Keep the icon size fixed by scrolling the dock.
+
+
+ 0.90
+ Dock height as a percentage of the desktop height.
+
+
+ false
+ Extend the dock to fill all the available height
+
+
+ [16, 22, 24, 32, 48, 64]
+ Default icon sizes for dash.
+
+
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index aec3427e0bb5e93ff60c9b1fbd3d32ed9f1252d3..f6c78b16238f1925b1f88d20d79c1ff587d37214 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -47,6 +47,7 @@
ui/closeDialog.js
ui/ctrlAltTab.js
ui/dash.js
+ ui/dock.js
ui/dateMenu.js
ui/dialog.js
ui/dnd.js
diff --git a/js/ui/dash.js b/js/ui/dash.js
index 6eb71e73bd7394ab9f06c3484f036d6849c1dcee..bbadc575b0dfbf3f5663029364f8e1fc701a4cd1 100644
--- a/js/ui/dash.js
+++ b/js/ui/dash.js
@@ -10,6 +10,9 @@ const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
+const Util = imports.misc.util;
+const Params = imports.misc.params;
+
var DASH_ANIMATION_TIME = 200;
var DASH_ITEM_LABEL_SHOW_TIME = 150;
var DASH_ITEM_LABEL_HIDE_TIME = 100;
@@ -29,6 +32,42 @@ class DashIcon extends AppDisplay.AppIcon {
setSizeManually: true,
showLabel: false,
});
+
+ this._resetId = 0;
+ this._cycleIndex = 0;
+ this._cycleWindows = null;
+ }
+
+ cycleWindows() {
+ const appWindows = this.app.get_windows().filter(w => !w.skip_taskbar);
+
+ if (appWindows.length < 1)
+ return;
+
+ if (this._resetId > 0)
+ GLib.source_remove(this._resetId);
+
+ this._resetId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 3500, () => {
+ this._resetId = 0;
+ this._cycleWindows = null;
+ this._cycleIndex = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+
+ if (this._cycleWindows === null || this._cycleWindows.length !== appWindows.length) {
+ this._cycleWindows = appWindows;
+ this._cycleIndex = 0;
+ }
+
+ if (this._cycleIndex >= this._cycleWindows.length - 1)
+ this._cycleIndex = 0;
+ else
+ this._cycleIndex += 1;
+
+ const window = this._cycleWindows[this._cycleIndex];
+
+ if (window)
+ Main.activateWindow(window);
}
// Disable all DnD methods
@@ -290,6 +329,8 @@ class DashActor extends St.Widget {
clip_to_allocation: true,
y_align: Clutter.ActorAlign.CENTER,
});
+
+ this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
}
vfunc_allocate(box, flags) {
@@ -334,43 +375,72 @@ class DashActor extends St.Widget {
const baseIconSizes = [16, 22, 24, 32, 48, 64];
var Dash = GObject.registerClass({
- Signals: { 'icon-size-changed': {} },
+ Signals: {
+ 'icon-size-changed': {},
+ 'menu-closed': {},
+ },
}, class Dash extends St.Bin {
- _init() {
+ _init(params) {
+ params = Params.parse(params, {
+ monitorIndex: -1,
+ fixedIconSize: false,
+ iconSize: 64,
+ iconSizes: baseIconSizes,
+ });
+
this._maxHeight = -1;
- this.iconSize = 64;
+ this._monitorIndex = params.monitorIndex;
+ this._iconSizeFixed = params.fixedIconSize;
+ this.iconSize = params.iconSize;
+ this.iconSizes = params.iconSizes;
this._shownInitially = false;
+ if (this._iconSizeFixed)
+ this._fixedIconSize = params.iconSize;
+ else
+ this._fixedIconSize = -1;
+
this._dragPlaceholder = null;
this._dragPlaceholderPos = -1;
this._animatingPlaceholdersCount = 0;
this._showLabelTimeoutId = 0;
this._resetHoverTimeoutId = 0;
+ this._ensureAppIconVisibilityTimeoutId = 0;
this._labelShowing = false;
-
this._container = new DashActor();
- this._box = new St.BoxLayout({ vertical: true,
- clip_to_allocation: true });
+ this._box = new St.BoxLayout({
+ vertical: true,
+ clip_to_allocation: false,
+ x_align: Clutter.ActorAlign.START,
+ y_align: Clutter.ActorAlign.START,
+ });
this._box._delegate = this;
- this._container.add_actor(this._box);
- this._container.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
-
+ this._scrollView = new St.ScrollView({
+ name: 'dockScrollView',
+ hscrollbar_policy: St.PolicyType.NEVER,
+ vscrollbar_policy: St.PolicyType.NEVER,
+ enable_mouse_scrolling: false,
+ });
+ this._scrollView.connect('scroll-event', this._onScrollEvent.bind(this));
+ this._container.add_actor(this._scrollView);
+ this._scrollView.add_actor(this._box);
this._showAppsIcon = new ShowAppsIcon();
- this._showAppsIcon.show(false);
+ this._showAppsIcon.show();
this._showAppsIcon.icon.setIconSize(this.iconSize);
this._hookUpLabel(this._showAppsIcon);
-
this.showAppsButton = this._showAppsIcon.toggleButton;
-
this._container.add_actor(this._showAppsIcon);
super._init({ child: this._container });
+
this.connect('notify::height', () => {
- if (this._maxHeight != this.height)
+ if (this._maxHeight !== this.height)
this._queueRedisplay();
this._maxHeight = this.height;
});
+ this._tracker = Shell.WindowTracker.get_default();
+
this._workId = Main.initializeDeferredWork(this._box, this._redisplay.bind(this));
this._appSystem = Shell.AppSystem.get_default();
@@ -379,19 +449,63 @@ var Dash = GObject.registerClass({
AppFavorites.getAppFavorites().reload();
this._queueRedisplay();
});
+
AppFavorites.getAppFavorites().connect('changed', this._queueRedisplay.bind(this));
this._appSystem.connect('app-state-changed', this._queueRedisplay.bind(this));
Main.overview.connect('item-drag-begin',
- this._onDragBegin.bind(this));
+ this._onDragBegin.bind(this));
Main.overview.connect('item-drag-end',
- this._onDragEnd.bind(this));
+ this._onDragEnd.bind(this));
Main.overview.connect('item-drag-cancelled',
- this._onDragCancelled.bind(this));
+ this._onDragCancelled.bind(this));
+ }
- // Translators: this is the name of the dock/favorites area on
- // the left of the overview
- Main.ctrlAltTabManager.addGroup(this, _("Dash"), 'user-bookmarks-symbolic');
+ setIconSize(size, fixed = false) {
+ this._iconSizeFixed = fixed;
+ this._fixedIconSize = size;
+ this._queueRedisplay();
+ }
+
+ setIconSizes(sizes) {
+ this.iconSizes = sizes;
+ this._queueRedisplay();
+ }
+
+ _onScrollEvent(_, event) {
+ // If scroll is not used because the icon is resized, let the scroll event propagate.
+ if (!this._iconSizeFixed)
+ return Clutter.EVENT_PROPAGATE;
+
+ // Reset timeout to avoid conflicting with the mouse hover event.
+ if (this._ensureAppIconVisibilityTimeoutId > 0) {
+ GLib.source_remove(this._ensureAppIconVisibilityTimeoutId);
+ this._ensureAppIconVisibilityTimeoutId = 0;
+ }
+
+ // Skip to prevent handling scroll twice.
+ if (event.is_pointer_emulated())
+ return Clutter.EVENT_STOP;
+
+ const adjustment = this._scrollView.get_vscroll_bar().get_adjustment();
+ const increment = adjustment.step_increment;
+
+ let value = adjustment.get_value();
+ let [, dy] = event.get_scroll_delta();
+
+ switch (event.get_scroll_direction()) {
+ case Clutter.ScrollDirection.UP:
+ value -= increment;
+ break;
+ case Clutter.ScrollDirection.DOWN:
+ value += increment;
+ break;
+ case Clutter.ScrollDirection.SMOOTH:
+ value += dy * increment;
+ break;
+ }
+ adjustment.set_value(value);
+ return Clutter.EVENT_STOP;
}
_onDragBegin() {
@@ -401,7 +515,7 @@ var Dash = GObject.registerClass({
};
DND.addDragMonitor(this._dragMonitor);
- if (this._box.get_n_children() == 0) {
+ if (this._box.get_n_children() === 0) {
this._emptyDropTarget = new EmptyDropTargetItem();
this._box.insert_child_at_index(this._emptyDropTarget, 0);
this._emptyDropTarget.show(true);
@@ -479,15 +593,44 @@ var Dash = GObject.registerClass({
_createAppItem(app) {
let appIcon = new DashIcon(app);
-
- appIcon.connect('menu-state-changed',
- (o, opened) => {
- this._itemMenuStateChanged(item, opened);
- });
-
let item = new DashItemContainer();
item.setChild(appIcon);
+ appIcon.connect('menu-state-changed', (_, opened) => {
+ if (!opened)
+ this.emit('menu-closed');
+
+ this._itemMenuStateChanged(item, opened);
+ });
+
+ appIcon.connect('notify::hover', () => {
+ if (appIcon.hover) {
+ this._ensureAppIconVisibilityTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
+ Util.ensureActorVisibleInScrollView(this._scrollView, appIcon);
+ this._ensureAppIconVisibilityTimeoutId = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+ } else if (this._ensureAppIconVisibilityTimeoutId > 0) {
+ GLib.source_remove(this._ensureAppIconVisibilityTimeoutId);
+ this._ensureAppIconVisibilityTimeoutId = 0;
+ }
+ });
+
+ appIcon.connect('clicked', actor => {
+ Util.ensureActorVisibleInScrollView(this._scrollView, actor);
+
+ if (!Main.overview._shown && appIcon.app == this._tracker.focus_app)
+ appIcon.cycleWindows();
+ });
+
+ appIcon.connect('key-focus-in', actor => {
+ let [xShift, yShift] = Util.ensureActorVisibleInScrollView(this._scrollView, actor);
+ if (appIcon._menu) {
+ appIcon._menu._boxPointer.xOffset = -xShift;
+ appIcon._menu._boxPointer.yOffset = -yShift;
+ }
+ });
+
// Override default AppIcon label_actor, now the
// accessible_name is set at DashItemContainer.setLabelText
appIcon.label_actor = null;
@@ -559,44 +702,49 @@ var Dash = GObject.registerClass({
actor.child._delegate.icon &&
!actor.animatingOut;
});
-
iconChildren.push(this._showAppsIcon);
- if (this._maxHeight == -1)
+ if (this._maxHeight === -1)
return;
- let themeNode = this._container.get_theme_node();
- let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0,
- x2: 42 /* whatever */,
- y2: this._maxHeight });
- let maxContent = themeNode.get_content_box(maxAllocation);
- let availHeight = maxContent.y2 - maxContent.y1;
- let spacing = themeNode.get_length('spacing');
-
- let firstButton = iconChildren[0].child;
- let firstIcon = firstButton._delegate.icon;
-
- // Enforce valid spacings during the size request
- firstIcon.icon.ensure_style();
- let [, iconHeight] = firstIcon.icon.get_preferred_height(-1);
- let [, buttonHeight] = firstButton.get_preferred_height(-1);
-
- // Subtract icon padding and box spacing from the available height
- availHeight -= iconChildren.length * (buttonHeight - iconHeight) +
- (iconChildren.length - 1) * spacing;
-
- let availSize = availHeight / iconChildren.length;
+ // Ensure the container is present on the stage to avoid lockscreen errors.
+ if (!this._container || !this._container.realized || !this._container.get_stage())
+ return;
- let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
- let iconSizes = baseIconSizes.map(s => s * scaleFactor);
+ let newIconSize;
- let newIconSize = baseIconSizes[0];
- for (let i = 0; i < iconSizes.length; i++) {
- if (iconSizes[i] < availSize)
- newIconSize = baseIconSizes[i];
+ if (this._iconSizeFixed) {
+ newIconSize = this._fixedIconSize;
+ } else {
+ let themeNode = this._container.get_theme_node();
+ let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0,
+ x2: 42 /* whatever */,
+ y2: this._maxHeight });
+ let maxContent = themeNode.get_content_box(maxAllocation);
+ let availHeight = maxContent.y2 - maxContent.y1;
+ let spacing = themeNode.get_length('spacing');
+
+ let firstButton = iconChildren[0].child;
+ let firstIcon = firstButton._delegate.icon;
+ // Enforce valid spacings during the size request
+ firstIcon.icon.ensure_style();
+ let [, iconHeight] = firstIcon.icon.get_preferred_height(-1);
+ let [, buttonHeight] = firstButton.get_preferred_height(-1);
+ // Subtract icon padding and box spacing from the available height
+ availHeight -= iconChildren.length * (buttonHeight - iconHeight) +
+ (iconChildren.length - 1) * spacing;
+ let availSize = availHeight / iconChildren.length;
+ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
+ let iconSizes = this.iconSizes.map(s => s * scaleFactor);
+
+ newIconSize = this.iconSizes[0];
+ for (let i = 0; i < iconSizes.length; i++) {
+ if (iconSizes[i] < availSize)
+ newIconSize = this.iconSizes[i];
+ }
}
- if (newIconSize == this.iconSize)
+ if (newIconSize === this.iconSize)
return;
let oldIconSize = this.iconSize;
@@ -614,8 +762,7 @@ var Dash = GObject.registerClass({
// Don't animate the icon size change when the overview
// is transitioning, not visible or when initially filling
// the dash
- if (!Main.overview.visible || Main.overview.animationInProgress ||
- !this._shownInitially)
+ if (Main.overview.animationInProgress || !this._shownInitially)
continue;
let [targetWidth, targetHeight] = icon.icon.get_size();
@@ -623,7 +770,7 @@ var Dash = GObject.registerClass({
// Scale the icon's texture to the previous size and
// tween to the new size
icon.icon.set_size(icon.icon.width * scale,
- icon.icon.height * scale);
+ icon.icon.height * scale);
icon.icon.ease({
width: targetWidth,
@@ -758,6 +905,12 @@ var Dash = GObject.registerClass({
for (let i = 0; i < addedItems.length; i++)
addedItems[i].item.show(animate);
+ this._scrollView.update_fade_effect(this.iconSize * 0.2, 0);
+ const effect = this._scrollView.get_effect('fade');
+
+ if (effect)
+ effect.fade_edges = true;
+
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
// Without it, StBoxLayout may use a stale size cache
this._box.queue_relayout();
diff --git a/js/ui/dock.js b/js/ui/dock.js
new file mode 100644
index 0000000000000000000000000000000000000000..64a87dd2cfcecdefd01fccb0cc592ea8dbcabe95
--- /dev/null
+++ b/js/ui/dock.js
@@ -0,0 +1,523 @@
+/* exported Dock, State, Mode */
+
+const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi;
+
+const Main = imports.ui.main;
+const Dash = imports.ui.dash;
+const OverviewControls = imports.ui.overviewControls;
+const ViewSelector = imports.ui.viewSelector;
+const Layout = imports.ui.layout;
+
+var State = {
+ HIDDEN: 0,
+ SHOWING: 1,
+ SHOWN: 2,
+ HIDING: 3,
+};
+
+var Mode = {
+ DEFAULT: 0,
+ AUTOHIDE: 1,
+ FIXED: 2,
+};
+
+var DOCK_PRESSURE_THRESHOLD = 100; // ms
+var DOCK_PRESSURE_TIMEOUT = 1000; // ms
+
+var DOCK_ANIMATION_DURATION = 0.2; // seconds
+var DOCK_SHOW_DELAY = 0.25; // seconds
+var DOCK_HIDE_DELAY = 0.2; // seconds
+
+var Dock = GObject.registerClass({
+ Signals: {
+ showing: {},
+ hiding: {},
+ },
+}, class Dock extends St.Bin {
+ _init(params) {
+ const { monitorIndex } = params;
+ this._monitorIndex = monitorIndex;
+ this._dockSettings = new Gio.Settings({ schema_id: 'org.gnome.shell.dock' });
+
+ this._dockMode = this._dockSettings.get_enum('mode');
+ // Update dock on settings changes
+ this._bindSettingsChanges();
+
+ // Used to ignore hover events while auto-hiding
+ this._ignoreHover = false;
+ this._saveIgnoreHover = null;
+
+ // Initial dock state.
+ this._dockState = State.HIDDEN;
+
+ // Get the monitor object for this dock.
+ this._monitor = Main.layoutManager.monitors[this._monitorIndex];
+
+ // Pressure barrier state
+ this._canUsePressure = false;
+ this._pressureBarrier = null;
+ this._barrier = null;
+
+ this._removeBarrierTimeoutId = 0;
+
+ // Fallback autohide detection
+ this._dockEdge = null;
+
+ // Get icon size settings.
+ const fixedIconSize = this._dockSettings.get_boolean('fixed-icon-size');
+ const iconSize = this._dockSettings.get_int('icon-size');
+ const iconSizes = this._getIconSizes();
+
+ // Create a new dash.
+ this.dash = new Dash.Dash({
+ monitorIndex,
+ fixedIconSize,
+ iconSizes,
+ iconSize,
+ });
+
+ super._init({
+ name: 'dock',
+ reactive: false,
+ });
+
+ this._box = new St.BoxLayout({
+ name: 'dockbox',
+ reactive: true,
+ track_hover: true,
+ });
+
+ this._box.connect('notify::hover', this._hoverChanged.bind(this));
+ this.dash.connect('menu-closed', () => {
+ this._box.sync_hover();
+ });
+
+ global.display.connect('workareas-changed', this._reallocate.bind(this));
+ global.display.connect('in-fullscreen-changed', this._updateBarrier.bind(this));
+
+ // Only initialize signals when the overview isn't a dummy.
+ if (!Main.overview.isDummy) {
+ Main.overview.connect('item-drag-begin', this._onDragStart.bind(this));
+ Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
+ Main.overview.connect('item-drag-cancelled', this._onDragEnd.bind(this));
+ Main.overview.connect('showing', this._onOverviewShowing.bind(this));
+ Main.overview.connect('hiding', this._onOverviewHiding.bind(this));
+ }
+
+ let id = this.connect_after('paint', () => {
+ this.disconnect(id);
+
+ this._updateDashVisibility();
+
+ // Setup barriers.
+ this._updatePressureBarrier();
+ this._updateBarrier();
+
+ // Setup the fallback autohide detection as needed.
+ this._setupFallbackEdgeIfNeeded();
+ });
+
+ this._box.add_actor(this.dash);
+ this._box.x_expand = true;
+
+ this._slider = new OverviewControls.DashSlider();
+ this._slider.add_actor(this._box);
+
+ this.dash.connect('icon-size-changed', this._animateIn.bind(this));
+
+ this.set_child(this._slider);
+
+ Main.uiGroup.add_child(this);
+
+ if (Main.uiGroup.contains(global.top_window_group))
+ Main.uiGroup.set_child_below_sibling(this, global.top_window_group);
+
+ this._updateTracking();
+
+ // Create and apply height constraint to the dash. It's controlled by this.height
+ let constraint = new Clutter.BindConstraint({
+ source: this,
+ coordinate: Clutter.BindCoordinate.HEIGHT,
+ });
+ this.dash.add_constraint(constraint);
+
+ // Set initial allocation based on work area.
+ this._reallocate();
+ }
+
+ _bindSettingsChanges() {
+ const settings = this._dockSettings;
+
+ ['changed::icon-size', 'changed::fixed-icon-size'].forEach(signal =>
+ settings.connect(signal,
+ () => {
+ const fixed = settings.get_boolean('fixed-icon-size');
+ const size = settings.get_int('icon-size');
+ this.dash.setIconSize(size, fixed);
+ },
+ ));
+
+ settings.connect('changed::icon-sizes',
+ () => {
+ const sizes = this._getIconSizes();
+ this.dash.setIconSizes(sizes);
+ });
+
+ settings.connect('changed::mode',
+ () => {
+ this._dockMode = settings.get_enum('mode');
+
+ this._updateTracking();
+ this._updateDashVisibility();
+ this._updateBarrier();
+ });
+
+ settings.connect('changed::extend', this._reallocate.bind(this));
+ settings.connect('changed::height', this._reallocate.bind(this));
+ }
+
+ _getIconSizes() {
+ const iconSizesVariant = this._dockSettings.get_value('icon-sizes');
+ const n = iconSizesVariant.n_children();
+ const iconSizes = [];
+
+ for (let i = 0; i < n; i++) {
+ const val = iconSizesVariant.get_child_value(i).get_int32();
+ iconSizes.push(val);
+ }
+
+ return iconSizes;
+ }
+
+ _updateDashVisibility() {
+ // Ignore if overview is visible.
+ if (Main.overview.visibleTarget)
+ return;
+
+ // If auto-hiding check that the dash should still be visible
+ if (this._dockMode === Mode.AUTOHIDE) {
+ this._ignoreHover = false;
+
+ global.sync_pointer();
+
+ if (this._box.hover)
+ this._animateIn();
+ else
+ this._animateOut();
+
+ } else if (this._dockMode === Mode.FIXED) {
+ this._animateIn();
+ } else {
+ this._animateOut();
+ }
+ }
+
+ _onOverviewShowing() {
+ this._ignoreHover = true;
+ this._removeTransitions();
+ this._animateIn();
+ }
+
+ _onOverviewHiding() {
+ this._ignoreHover = false;
+ this._updateDashVisibility();
+ }
+
+ _hoverChanged() {
+ if (!this._ignoreHover && this._dockMode === Mode.AUTOHIDE) {
+ if (this._box.hover)
+ this.slideIn();
+ else
+ this.slideOut();
+
+ }
+ }
+
+ slideIn() {
+ if (this._dockState === State.HIDDEN || this._dockState === State.HIDING) {
+ if (this._dockState === State.HIDING)
+ this._removeTransitions();
+
+ // Prevent 'double' animations which can cause visual quirks with autohide.
+ if (this._dockState !== State.SHOWING) {
+ this.emit('showing');
+ this._animateIn();
+ }
+ }
+ }
+
+ slideOut() {
+ if (this._dockState === State.SHOWN || this._dockState === State.SHOWING) {
+ let delay = DOCK_HIDE_DELAY;
+
+ // If the dock is already animating in, wait until it is finished.
+ if (this._dockState === State.SHOWING)
+ delay += DOCK_ANIMATION_DURATION;
+
+ this.emit('hiding');
+ this._animateOut(delay);
+ }
+ }
+
+ _animateIn(delay = 0) {
+ this._dockState = State.SHOWING;
+
+ this._slider.slideIn(DOCK_ANIMATION_DURATION, delay, () => {
+ this._dockState = State.SHOWN;
+
+ if (this._removeBarrierTimeoutId > 0)
+ GLib.source_remove(this._removeBarrierTimeoutId);
+
+ // Only schedule a remove timeout if the barrier exists.
+ if (this._barrier)
+ this._removeBarrierTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, this._removeBarrier.bind(this));
+ });
+ }
+
+ _animateOut(delay = 0) {
+ this._dockState = State.HIDING;
+
+ this._slider.slideOut(DOCK_ANIMATION_DURATION, delay, () => {
+ this._dockState = State.HIDDEN;
+
+ // Remove queued barrier removal if any
+ if (this._removeBarrierTimeoutId > 0)
+ GLib.source_remove(this._removeBarrierTimeoutId);
+
+ this._updateBarrier();
+ });
+ }
+
+ _setupFallbackEdgeIfNeeded() {
+ this._canUsePressure = global.display.supports_extended_barriers();
+
+ if (this._dockEdge) {
+ this._dockEdge.destroy();
+ this._dockEdge = null;
+ }
+
+ if (!this._canUsePressure) {
+ log('Dock is using fallback edge detection.');
+
+ const workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
+ const height = workArea.height - 3;
+ this._dockEdge = new Clutter.Actor({
+ name: 'dock-edge',
+ width: 1,
+ height,
+ opacity: 0,
+ reactive: true,
+ });
+
+ Main.layoutManager.addChrome(this._dockEdge);
+
+ if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) {
+ this._dockEdge.set_position(workArea.width - this._dockEdge.width, 0);
+ this.set_anchor_point_from_gravity(Clutter.Gravity.EAST);
+ } else {
+ this._dockEdge.set_position(0, 1);
+ }
+
+ this._dockEdge.connect('enter-event', () => {
+ if (!this._dockEntered) {
+ this._dockEntered = true;
+ if (!this._monitor.inFullscreen)
+ this._onPressureSensed();
+
+ }
+ return Clutter.EVENT_PROPAGATE;
+ });
+
+ this._dockEdge.connect('leave-event', (_, event) => {
+ if (event.get_related() !== this._dockEdge)
+ this._dockEntered = false;
+
+ return Clutter.EVENT_STOP;
+ });
+ }
+ }
+
+ _updatePressureBarrier() {
+ this._canUsePressure = global.display.supports_extended_barriers();
+
+ // Remove existing barriers
+
+ if (this._pressureBarrier) {
+ this._pressureBarrier.destroy();
+ this._pressureBarrier = null;
+ }
+
+ if (this._barrier) {
+ this._barrier.destroy();
+ this._barrier = null;
+ }
+
+ if (this._canUsePressure) {
+ this._pressureBarrier = new Layout.PressureBarrier(DOCK_PRESSURE_THRESHOLD, DOCK_PRESSURE_TIMEOUT + DOCK_SHOW_DELAY * 1000, // 1 second plus the delay to show.
+ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW);
+ this._pressureBarrier.connect('trigger', () => {
+ if (this._monitor.inFullscreen)
+ return;
+ this._onPressureSensed();
+ });
+ }
+ }
+
+ _onPressureSensed() {
+ if (Main.overview.visibleTarget)
+ return;
+
+ // After any animations have completed, check that the mouse hasn't left the
+ // dock area.
+ GLib.timeout_add(GLib.PRIORITY_DEFAULT, DOCK_ANIMATION_DURATION * 1000, () => {
+ let [x, y] = global.get_pointer();
+ const computedX = this.x + this._slider.x;
+ const computedY = this._monitor.y + this._monitor.height;
+
+ if (x > computedX ||
+ x < this._monitor.x ||
+ y < this._monitor.y ||
+ y > computedY) {
+ this._hoverChanged();
+ return GLib.SOURCE_REMOVE;
+ } else {
+ return GLib.SOURCE_CONTINUE;
+ }
+ });
+
+ this.slideIn();
+ }
+
+ _removeBarrier() {
+ if (this._barrier) {
+ if (this._pressureBarrier)
+ this._pressureBarrier.removeBarrier(this._barrier);
+
+ this._barrier.destroy();
+ this._barrier = null;
+ }
+ this._removeBarrierTimeoutId = 0;
+ return false;
+ }
+
+ _updateBarrier() {
+ // Remove existing barrier
+ this._removeBarrier();
+
+ // The barrier should not be set in fullscreen.
+ if (this._monitor.inFullscreen)
+ return;
+
+ // Reset the barrier to default variables.
+ if (this._pressureBarrier) {
+ this._pressureBarrier._reset();
+ this._pressureBarrier._isTriggered = false;
+ }
+
+ if (this._canUsePressure && this._dockMode === Mode.AUTOHIDE) {
+ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index);
+
+ let x1 = this._monitor.x + 1;
+ let x2 = x1;
+ let y1 = workArea.y + 1;
+ // Avoid conflicting with the hot corner by subtracting 1px;
+ let y2 = workArea.y + workArea.height - 1;
+ let direction = Meta.BarrierDirection.POSITIVE_X;
+
+ if (this._pressureBarrier && this._dockState === State.HIDDEN) {
+ this._barrier = new Meta.Barrier({
+ display: global.display,
+ x1,
+ x2,
+ y1,
+ y2,
+ directions: direction,
+ });
+
+ this._pressureBarrier.addBarrier(this._barrier);
+ }
+ }
+ }
+
+ _updateTracking() {
+ Main.layoutManager._untrackActor(this._slider);
+ Main.layoutManager._untrackActor(this);
+
+ if (this._dockMode === Mode.FIXED) {
+ // Setting trackFullscreen directly on the slider
+ // causes issues when full-screening some windows.
+ Main.layoutManager._trackActor(this, {
+ affectsInputRegion: false,
+ trackFullscreen: true,
+ });
+ Main.layoutManager._trackActor(this._slider, { affectsStruts: true });
+ } else {
+ Main.layoutManager._trackActor(this._slider);
+ }
+ }
+
+ _reallocate() {
+ // Ensure all height-related variables are updated.
+ this._updateDashVisibility();
+
+ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
+
+ let extendHeight = this._dockSettings.get_boolean('extend');
+ let height = extendHeight ? 1 : this._dockSettings.get_double('height');
+
+ if (height < 0 || height > 1)
+ height = 0.95;
+
+ this.height = Math.round(height * workArea.height);
+ this.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_WEST);
+ this.x = this._monitor.x;
+ this.y = workArea.y + Math.round(((1 - height) / 2) * workArea.height);
+
+ if (extendHeight) {
+ this.dash._container.set_height(this.height);
+ this.add_style_class_name('extended');
+ } else {
+ this.dash._container.set_height(-1);
+ this.remove_style_class_name('extended');
+ }
+ }
+
+ _removeTransitions() {
+ this._slider.remove_all_transitions();
+ }
+
+ _onDragStart() {
+ this._saveIgnoreHover = this._ignoreHover;
+ this._ignoreHover = true;
+ this._animateIn();
+ }
+
+ _onDragEnd() {
+ if (this._saveIgnoreHover !== null)
+ this._ignoreHover = this._saveIgnoreHover;
+
+ this._saveIgnoreHover = null;
+ this._box.sync_hover();
+
+ if (Main.overview.slideInn)
+ this._pageChanged();
+
+ }
+
+ _pageChanged() {
+ const activePage = Main.overview.viewSelector.getActivePage();
+ const showDash = activePage === ViewSelector.ViewPage.WINDOWS ||
+ activePage === ViewSelector.ViewPage.APPS;
+
+ if (showDash)
+ this._animateIn();
+ else
+ this._animateOut();
+
+ }
+
+ _onAccessibilityFocus() {
+ this._box.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
+ this._animateIn();
+ }
+});
diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js
index cc430f918d7dafc9b9941e11dfcbc5bf141e0af0..7e3597b6c7ef28c65b5000b9674af0786c62d2af 100644
--- a/js/ui/overviewControls.js
+++ b/js/ui/overviewControls.js
@@ -1,9 +1,9 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
-/* exported ControlsManager */
+/* exported ControlsManager, DashSlider */
const { Clutter, GObject, Meta, St } = imports.gi;
-const Dash = imports.ui.dash;
+const Dock = imports.ui.dock;
const Main = imports.ui.main;
const Params = imports.misc.params;
const ViewSelector = imports.ui.viewSelector;
@@ -324,23 +324,12 @@ class ThumbnailsSlider extends SlidingControl {
var DashSlider = GObject.registerClass(
class DashSlider extends SlidingControl {
- _init(dash) {
+ _init() {
super._init({ slideDirection: SlideDirection.LEFT });
- this._dash = dash;
-
- // SlideLayout reads the actor's expand flags to decide
- // whether to allocate the natural size to its child, or the whole
- // available allocation
- this._dash.x_expand = true;
-
this.x_expand = true;
this.x_align = Clutter.ActorAlign.START;
this.y_expand = true;
-
- this.add_actor(this._dash);
-
- this._dash.connect('icon-size-changed', this._updateSlide.bind(this));
}
_getSlide() {
@@ -350,12 +339,26 @@ class DashSlider extends SlidingControl {
return 0;
}
- _onWindowDragBegin() {
- this.fadeHalf();
+ _onOverviewHiding() {}
+
+ _updateSlide(duration, delay = 0, onComplete = () => {}) {
+ const slide = this._getSlide();
+ this.ease_property('@layout.slide-x', slide, {
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ duration: duration * 1000,
+ delay: delay * 1000,
+ onComplete,
+ });
}
- _onWindowDragEnd() {
- this.fadeIn();
+ slideIn(duration, delay = 0, onComplete = () => { }) {
+ this._visible = true;
+ this._updateSlide(duration, delay, onComplete);
+ }
+
+ slideOut(duration, delay = 0, onComplete = () => { }) {
+ this._visible = false;
+ this._updateSlide(duration, delay, onComplete);
}
});
@@ -413,10 +416,11 @@ class ControlsManager extends St.Widget {
clip_to_allocation: true,
});
- this.dash = new Dash.Dash();
- this._dashSlider = new DashSlider(this.dash);
+ this.dock = new Dock.Dock({
+ monitorIndex: Main.layoutManager.primaryIndex,
+ });
this._dashSpacer = new DashSpacer();
- this._dashSpacer.setDashActor(this._dashSlider);
+ this._dashSpacer.setDashActor(this.dock._slider);
let workspaceManager = global.workspace_manager;
let activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
@@ -447,8 +451,6 @@ class ControlsManager extends St.Widget {
x_expand: true, y_expand: true });
this.add_actor(this._group);
- this.add_actor(this._dashSlider);
-
this._group.add_actor(this._dashSpacer);
this._group.add_child(this.viewSelector);
this._group.add_actor(this._thumbnailsSlider);
@@ -460,6 +462,10 @@ class ControlsManager extends St.Widget {
this.connect('destroy', this._onDestroy.bind(this));
}
+ get dash() {
+ return this.dock.dash;
+ }
+
_onDestroy() {
global.workspace_manager.disconnect(this._nWorkspacesNotifyId);
}
@@ -482,7 +488,7 @@ class ControlsManager extends St.Widget {
let geometry = { x, y, width, height };
let spacing = this.get_theme_node().get_length('spacing');
- let dashWidth = this._dashSlider.getVisibleWidth() + spacing;
+ let dashWidth = this.dock._slider.getVisibleWidth() + spacing;
let thumbnailsWidth = this._thumbnailsSlider.getNonExpandedWidth() + spacing;
geometry.width -= dashWidth;
@@ -511,9 +517,9 @@ class ControlsManager extends St.Widget {
let thumbnailsVisible = activePage == ViewSelector.ViewPage.WINDOWS;
if (dashVisible)
- this._dashSlider.slideIn();
+ this.dock.slideIn();
else
- this._dashSlider.slideOut();
+ this.dock.slideOut();
if (thumbnailsVisible)
this._thumbnailsSlider.slideIn();
@@ -530,7 +536,7 @@ class ControlsManager extends St.Widget {
}
_onPageEmpty() {
- this._dashSlider.pageEmpty();
+ this.dock._slider.pageEmpty();
this._thumbnailsSlider.pageEmpty();
this._updateSpacerVisibility();
diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js
index 5c0dd9bb750fb0ebd215d54099560167f873cc2a..1acd8c881c72ea3073d1bfa5cb09cea0e44cd8ac 100644
--- a/js/ui/viewSelector.js
+++ b/js/ui/viewSelector.js
@@ -137,7 +137,7 @@ var ViewSelector = GObject.registerClass({
this._showAppsButton = showAppsButton;
this._showAppsButton.connect('notify::checked', this._onShowAppsButtonToggled.bind(this));
- this._activePage = null;
+ this._activePage = this._workspacesPage;
this._searchActive = false;
@@ -395,8 +395,14 @@ var ViewSelector = GObject.registerClass({
}
_onShowAppsButtonToggled() {
- this._showPage(this._showAppsButton.checked
- ? this._appsPage : this._workspacesPage);
+ if (this._showAppsButton.checked) {
+ if (!Main.overview.visibleTarget)
+ Main.overview.show();
+
+ this._showPage(this._appsPage);
+ } else {
+ this._showPage(this._workspacesPage);
+ }
}
_onStageKeyPress(actor, event) {