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) {