2025-02-09 23:09:13 +01:00
/ * *
2025-02-09 23:13:53 +01:00
* V - Shell ( Vertical Workspaces )
2025-02-09 23:09:13 +01:00
* workspaceThumbnail . js
2025-02-09 23:13:53 +01:00
*
2025-02-09 23:09:13 +01:00
* @ author GdH < G - dH @ github . com >
* @ copyright 2022 - 2023
* @ license GPL - 3.0
*
* /
'use strict' ;
2025-02-09 23:16:18 +01:00
import GLib from 'gi://GLib' ;
import Clutter from 'gi://Clutter' ;
import St from 'gi://St' ;
import Meta from 'gi://Meta' ;
import Shell from 'gi://Shell' ;
import * as Main from 'resource:///org/gnome/shell/ui/main.js' ;
import * as DND from 'resource:///org/gnome/shell/ui/dnd.js' ;
import * as AppDisplay from 'resource:///org/gnome/shell/ui/appDisplay.js' ;
import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js' ;
import * as WorkspaceThumbnail from 'resource:///org/gnome/shell/ui/workspaceThumbnail.js' ;
import * as Background from 'resource:///org/gnome/shell/ui/background.js' ;
let Me ;
let opt ;
const ThumbnailState = {
NEW : 0 ,
EXPANDING : 1 ,
EXPANDED : 2 ,
ANIMATING _IN : 3 ,
NORMAL : 4 ,
REMOVING : 5 ,
ANIMATING _OUT : 6 ,
ANIMATED _OUT : 7 ,
COLLAPSING : 8 ,
DESTROYED : 9 ,
} ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
const ControlsState = OverviewControls . ControlsState ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
const WORKSPACE _CUT _SIZE = 10 ;
const WORKSPACE _KEEP _ALIVE _TIME = 100 ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
export const WorkspaceThumbnailModule = class {
constructor ( me ) {
Me = me ;
opt = Me . opt ;
2025-02-09 23:13:53 +01:00
2025-02-09 23:16:18 +01:00
this . _firstActivation = true ;
this . moduleEnabled = false ;
this . _overrides = null ;
}
2025-02-09 23:13:53 +01:00
2025-02-09 23:16:18 +01:00
cleanGlobals ( ) {
Me = null ;
opt = null ;
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
update ( reset ) {
this . moduleEnabled = true ;
const conflict = false ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
reset = reset || ! this . moduleEnabled || conflict ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
// don't touch the original code if module disabled
if ( reset && ! this . _firstActivation ) {
this . _disableModule ( ) ;
} else if ( ! reset ) {
this . _firstActivation = false ;
this . _activateModule ( ) ;
}
if ( reset && this . _firstActivation )
console . debug ( ' WorkspaceThumbnailModule - Keeping untouched' ) ;
}
2025-02-09 23:13:53 +01:00
2025-02-09 23:16:18 +01:00
_activateModule ( ) {
if ( ! this . _overrides )
this . _overrides = new Me . Util . Overrides ( ) ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
// don't limit max thumbnail scale for other clients than overview, specifically AATWS.
// this variable is not yet implemented in 45.beta.1
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
this . _overrides . addOverride ( 'WorkspaceThumbnail' , WorkspaceThumbnail . WorkspaceThumbnail . prototype , WorkspaceThumbnailCommon ) ;
this . _overrides . addOverride ( 'ThumbnailsBoxCommon' , WorkspaceThumbnail . ThumbnailsBox . prototype , ThumbnailsBoxCommon ) ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
// replacing opt.ORIENTATION local constant with boxOrientation internal variable allows external customers such as the AATWS extension to control the box orientation.
Main . overview . _overview . controls . _thumbnailsBox . _boxOrientation = opt . ORIENTATION ;
2025-02-09 23:13:53 +01:00
2025-02-09 23:16:18 +01:00
console . debug ( ' WorkspaceThumbnailModule - Activated' ) ;
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
_disableModule ( ) {
if ( this . _overrides )
this . _overrides . removeAll ( ) ;
this . _overrides = null ;
console . debug ( ' WorkspaceThumbnailModule - Disabled' ) ;
}
} ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
const WorkspaceThumbnailCommon = {
// injection to _init()
after _ _init ( ) {
// layout manager allows aligning widget children
this . layout _manager = new Clutter . BinLayout ( ) ;
// adding layout manager to tmb widget breaks wallpaper background aligning and rounded corners
// unless border is removed
if ( opt . SHOW _WS _TMB _BG )
this . add _style _class _name ( 'ws-tmb-labeled' ) ;
2025-02-09 23:16:18 +01:00
else
this . add _style _class _name ( 'ws-tmb-transparent' ) ;
2025-02-09 23:09:13 +01:00
// add workspace thumbnails labels if enabled
if ( opt . SHOW _WST _LABELS ) { // 0 - disable
2025-02-09 23:13:53 +01:00
const getLabel = function ( ) {
2025-02-09 23:09:13 +01:00
const wsIndex = this . metaWorkspace . index ( ) ;
let label = ` ${ wsIndex + 1 } ` ;
if ( opt . SHOW _WST _LABELS === 2 ) { // 2 - index + workspace name
2025-02-09 23:16:18 +01:00
const settings = Me . getSettings ( 'org.gnome.desktop.wm.preferences' ) ;
2025-02-09 23:09:13 +01:00
const wsLabels = settings . get _strv ( 'workspace-names' ) ;
2025-02-09 23:13:53 +01:00
if ( wsLabels . length > wsIndex && wsLabels [ wsIndex ] )
2025-02-09 23:09:13 +01:00
label += ` : ${ wsLabels [ wsIndex ] } ` ;
} else if ( opt . SHOW _WST _LABELS === 3 ) { // 3- index + app name
// global.display.get_tab_list offers workspace filtering using the second argument, but...
// ... it sometimes includes windows from other workspaces, like minimized VBox machines, after Shell restarts
const metaWin = global . display . get _tab _list ( 0 , null ) . filter (
w => w . get _monitor ( ) === this . monitorIndex && w . get _workspace ( ) . index ( ) === wsIndex ) [ 0 ] ;
if ( metaWin ) {
2025-02-09 23:13:53 +01:00
const tracker = Shell . WindowTracker . get _default ( ) ;
const app = tracker . get _window _app ( metaWin ) ;
label += ` : ${ app ? app . get _name ( ) : '' } ` ;
2025-02-09 23:09:13 +01:00
}
} else if ( opt . SHOW _WST _LABELS === 4 ) {
const metaWin = global . display . get _tab _list ( 0 , null ) . filter (
w => w . get _monitor ( ) === this . monitorIndex && w . get _workspace ( ) . index ( ) === wsIndex ) [ 0 ] ;
2025-02-09 23:13:53 +01:00
if ( metaWin )
2025-02-09 23:09:13 +01:00
label += ` : ${ metaWin . title } ` ;
}
return label ;
} . bind ( this ) ;
const label = getLabel ( ) ;
this . _wsLabel = new St . Label ( {
text : label ,
style _class : 'ws-tmb-label' ,
x _align : Clutter . ActorAlign . FILL ,
y _align : Clutter . ActorAlign . END ,
x _expand : true ,
y _expand : true ,
} ) ;
this . _wsLabel . _maxOpacity = 255 ;
this . _wsLabel . opacity = this . _wsLabel . _maxOpacity ;
this . add _child ( this . _wsLabel ) ;
this . set _child _above _sibling ( this . _wsLabel , null ) ;
2025-02-09 23:13:53 +01:00
this . _wsIndexConId = this . metaWorkspace . connect ( 'notify::workspace-index' , ( ) => {
2025-02-09 23:09:13 +01:00
const newLabel = getLabel ( ) ;
this . _wsLabel . text = newLabel ;
2025-02-09 23:13:53 +01:00
// avoid possibility of accessing non existing ws
if ( this . _updateLabelTimeout ) {
GLib . source _remove ( this . _updateLabelTimeout ) ;
this . _updateLabelTimeout = 0 ;
}
} ) ;
this . _nWindowsConId = this . metaWorkspace . connect ( 'notify::n-windows' , ( ) => {
2025-02-09 23:16:18 +01:00
if ( this . _updateLabelTimeout )
return ;
// wait for new data
2025-02-09 23:13:53 +01:00
this . _updateLabelTimeout = GLib . timeout _add ( GLib . PRIORITY _DEFAULT , 250 , ( ) => {
const newLabel = getLabel ( ) ;
this . _wsLabel . text = newLabel ;
this . _updateLabelTimeout = 0 ;
return GLib . SOURCE _REMOVE ;
} ) ;
2025-02-09 23:09:13 +01:00
} ) ;
2025-02-09 23:13:53 +01:00
}
if ( opt . CLOSE _WS _BUTTON _MODE ) {
const closeButton = new St . Icon ( {
style _class : 'workspace-close-button' ,
icon _name : 'window-close-symbolic' ,
x _align : Clutter . ActorAlign . END ,
y _align : Clutter . ActorAlign . START ,
x _expand : true ,
y _expand : true ,
reactive : true ,
opacity : 0 ,
} ) ;
closeButton . connect ( 'button-release-event' , ( ) => {
if ( opt . CLOSE _WS _BUTTON _MODE ) {
this . _closeWorkspace ( ) ;
return Clutter . EVENT _STOP ;
} else {
return Clutter . EVENT _PROPAGATE ;
}
} ) ;
closeButton . connect ( 'button-press-event' , ( ) => {
return Clutter . EVENT _STOP ;
} ) ;
closeButton . connect ( 'enter-event' , ( ) => {
closeButton . opacity = 255 ;
if ( ! Meta . prefs _get _dynamic _workspaces ( ) || ( Meta . prefs _get _dynamic _workspaces ( ) && global . workspace _manager . get _n _workspaces ( ) - 1 !== this . metaWorkspace . index ( ) ) ) {
// color the button red if ready to react on clicks
2025-02-09 23:16:18 +01:00
if ( opt . CLOSE _WS _BUTTON _MODE < 3 || ( opt . CLOSE _WS _BUTTON _MODE === 3 && Me . Util . isCtrlPressed ( ) ) )
2025-02-09 23:13:53 +01:00
closeButton . add _style _class _name ( 'workspace-close-button-hover' ) ;
}
} ) ;
closeButton . connect ( 'leave-event' , ( ) => {
closeButton . remove _style _class _name ( 'workspace-close-button-hover' ) ;
} ) ;
this . add _child ( closeButton ) ;
this . _closeButton = closeButton ;
this . reactive = true ;
this . _lastCloseClickTime = 0 ;
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
if ( opt . SHOW _WST _LABELS _ON _HOVER )
this . _wsLabel . opacity = 0 ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
this . connect ( 'enter-event' , ( ) => {
if ( opt . CLOSE _WS _BUTTON _MODE && ( ! Meta . prefs _get _dynamic _workspaces ( ) || ( Meta . prefs _get _dynamic _workspaces ( ) && global . workspace _manager . get _n _workspaces ( ) - 1 !== this . metaWorkspace . index ( ) ) ) )
this . _closeButton . opacity = 200 ;
2025-02-09 23:09:13 +01:00
if ( opt . SHOW _WST _LABELS _ON _HOVER ) {
2025-02-09 23:13:53 +01:00
this . _wsLabel . ease ( {
2025-02-09 23:09:13 +01:00
duration : 100 ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
2025-02-09 23:13:53 +01:00
opacity : this . _wsLabel . _maxOpacity ,
} ) ;
}
} ) ;
this . connect ( 'leave-event' , ( ) => {
this . _closeButton . opacity = 0 ;
if ( opt . SHOW _WST _LABELS _ON _HOVER ) {
this . _wsLabel . ease ( {
2025-02-09 23:09:13 +01:00
duration : 100 ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
2025-02-09 23:13:53 +01:00
opacity : 0 ,
} ) ;
2025-02-09 23:09:13 +01:00
}
2025-02-09 23:13:53 +01:00
} ) ;
2025-02-09 23:09:13 +01:00
if ( opt . SHOW _WS _TMB _BG ) {
this . _bgManager = new Background . BackgroundManager ( {
monitorIndex : this . monitorIndex ,
container : this . _viewport ,
vignette : false ,
controlPosition : false ,
} ) ;
this . _viewport . set _child _below _sibling ( this . _bgManager . backgroundActor , null ) ;
2025-02-09 23:13:53 +01:00
// full brightness of the thumbnail bg draws unnecessary attention
// there is a grey bg under the wallpaper
2025-02-09 23:09:13 +01:00
this . _bgManager . backgroundActor . opacity = 220 ;
2025-02-09 23:13:53 +01:00
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
this . connect ( 'destroy' , ( ) => {
if ( this . _wsIndexConId )
this . metaWorkspace . disconnect ( this . _wsIndexConId ) ;
if ( this . _nWindowsConId )
this . metaWorkspace . disconnect ( this . _nWindowsConId ) ;
if ( this . _updateLabelTimeout )
GLib . source _remove ( this . _updateLabelTimeout ) ;
if ( this . _bgManager )
this . _bgManager . destroy ( ) ;
} ) ;
} ,
_closeWorkspace ( ) {
// CLOSE_WS_BUTTON_MODE 1: single click, 2: double-click, 3: Ctrl
if ( opt . CLOSE _WS _BUTTON _MODE === 2 ) {
const doubleClickTime = Clutter . Settings . get _default ( ) . double _click _time ;
const clickDelay = Date . now ( ) - this . _lastCloseClickTime ;
if ( clickDelay > doubleClickTime ) {
this . _lastCloseClickTime = Date . now ( ) ;
return ;
}
2025-02-09 23:16:18 +01:00
} else if ( opt . CLOSE _WS _BUTTON _MODE === 3 && ! Me . Util . isCtrlPressed ( ) ) {
2025-02-09 23:13:53 +01:00
return ;
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
// close windows on this monitor
const windows = global . display . get _tab _list ( 0 , null ) . filter (
w => w . get _monitor ( ) === this . monitorIndex && w . get _workspace ( ) === this . metaWorkspace
) ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
for ( let i = 0 ; i < windows . length ; i ++ ) {
if ( ! windows [ i ] . is _on _all _workspaces ( ) )
windows [ i ] . delete ( global . get _current _time ( ) + i ) ;
2025-02-09 23:09:13 +01:00
}
} ,
2025-02-09 23:13:53 +01:00
activate ( time ) {
2025-02-09 23:09:13 +01:00
if ( this . state > ThumbnailState . NORMAL )
return ;
// if Static Workspace overview mode active, a click on the already active workspace should activate the window picker mode
const wsIndex = this . metaWorkspace . index ( ) ;
const lastWsIndex = global . display . get _workspace _manager ( ) . get _n _workspaces ( ) - 1 ;
const stateAdjustment = Main . overview . _overview . controls . _stateAdjustment ;
if ( stateAdjustment . value === ControlsState . APP _GRID ) {
if ( this . metaWorkspace . active ) {
Main . overview . _overview . controls . _shiftState ( Meta . MotionDirection . DOWN ) ;
// if searchActive, hide it immediately
Main . overview . searchEntry . set _text ( '' ) ;
} else {
this . metaWorkspace . activate ( time ) ;
}
2025-02-09 23:13:53 +01:00
} else if ( opt . OVERVIEW _MODE2 && ! opt . WORKSPACE _MODE && wsIndex < lastWsIndex ) {
if ( stateAdjustment . value > 1 )
stateAdjustment . value = 1 ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
// spread windows
// in OVERVIEW MODE 2 windows are not spread and workspace is not scaled
// we need to repeat transition to the overview state 1 (window picker), but with spreading windows animation
if ( this . metaWorkspace . active ) {
Main . overview . _overview . controls . _searchController . _setSearchActive ( false ) ;
opt . WORKSPACE _MODE = 1 ;
// setting value to 0 would reset WORKSPACE_MODE
stateAdjustment . value = 0.01 ;
stateAdjustment . ease ( 1 , {
duration : 200 ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
2025-02-09 23:09:13 +01:00
} else {
2025-02-09 23:13:53 +01:00
// switch ws
2025-02-09 23:09:13 +01:00
this . metaWorkspace . activate ( time ) ;
}
2025-02-09 23:13:53 +01:00
// a click on the current workspace should go back to the main view
} else if ( this . metaWorkspace . active ) {
Main . overview . hide ( ) ;
} else {
this . metaWorkspace . activate ( time ) ;
2025-02-09 23:09:13 +01:00
}
2025-02-09 23:13:53 +01:00
} ,
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
// Draggable target interface used only by ThumbnailsBox
handleDragOverInternal ( source , actor , time ) {
if ( source === Main . xdndHandler ) {
this . metaWorkspace . activate ( time ) ;
return DND . DragMotionResult . CONTINUE ;
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
if ( this . state > ThumbnailState . NORMAL )
return DND . DragMotionResult . CONTINUE ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
if ( source . metaWindow &&
! this . _isMyWindow ( source . metaWindow . get _compositor _private ( ) ) )
return DND . DragMotionResult . MOVE _DROP ;
if ( source . app && source . app . can _open _new _window ( ) )
return DND . DragMotionResult . COPY _DROP ;
if ( ! source . app && source . shellWorkspaceLaunch )
return DND . DragMotionResult . COPY _DROP ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:16:18 +01:00
if ( source instanceof AppDisplay . FolderIcon )
2025-02-09 23:13:53 +01:00
return DND . DragMotionResult . COPY _DROP ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
return DND . DragMotionResult . CONTINUE ;
} ,
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
acceptDropInternal ( source , actor , time ) {
if ( this . state > ThumbnailState . NORMAL )
return false ;
if ( source . metaWindow ) {
let win = source . metaWindow . get _compositor _private ( ) ;
if ( this . _isMyWindow ( win ) )
return false ;
let metaWindow = win . get _meta _window ( ) ;
Main . moveWindowToMonitorAndWorkspace ( metaWindow ,
this . monitorIndex , this . metaWorkspace . index ( ) ) ;
return true ;
} else if ( source . app && source . app . can _open _new _window ( ) ) {
if ( source . animateLaunchAtPos )
source . animateLaunchAtPos ( actor . x , actor . y ) ;
source . app . open _new _window ( this . metaWorkspace . index ( ) ) ;
return true ;
} else if ( ! source . app && source . shellWorkspaceLaunch ) {
// While unused in our own drag sources, shellWorkspaceLaunch allows
// extensions to define custom actions for their drag sources.
source . shellWorkspaceLaunch ( {
workspace : this . metaWorkspace . index ( ) ,
timestamp : time ,
} ) ;
return true ;
2025-02-09 23:16:18 +01:00
} else if ( source instanceof AppDisplay . FolderIcon ) {
for ( let app of source . view . _apps ) {
// const app = Shell.AppSystem.get_default().lookup_app(id);
app . open _new _window ( this . metaWorkspace . index ( ) ) ;
2025-02-09 23:13:53 +01:00
}
2025-02-09 23:09:13 +01:00
}
2025-02-09 23:13:53 +01:00
return false ;
} ,
} ;
const ThumbnailsBoxCommon = {
after _ _init ( scrollAdjustment , monitorIndex , orientation = opt . ORIENTATION ) {
this . _boxOrientation = orientation ;
} ,
_activateThumbnailAtPoint ( stageX , stageY , time , activateCurrent = false ) {
if ( activateCurrent ) {
const thumbnail = this . _thumbnails . find ( t => t . metaWorkspace . active ) ;
if ( thumbnail )
thumbnail . activate ( time ) ;
return ;
2025-02-09 23:09:13 +01:00
}
2025-02-09 23:13:53 +01:00
const [ r _ , x , y ] = this . transform _stage _point ( stageX , stageY ) ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
let thumbnail ;
if ( this . _boxOrientation )
thumbnail = this . _thumbnails . find ( t => y >= t . y && y <= t . y + t . height ) ;
else
thumbnail = this . _thumbnails . find ( t => x >= t . x && x <= t . x + t . width ) ;
if ( thumbnail )
thumbnail . activate ( time ) ;
2025-02-09 23:09:13 +01:00
} ,
2025-02-09 23:13:53 +01:00
acceptDrop ( source , actor , x , y , time ) {
if ( this . _dropWorkspace !== - 1 ) {
return this . _thumbnails [ this . _dropWorkspace ] . acceptDropInternal ( source , actor , time ) ;
} else if ( this . _dropPlaceholderPos !== - 1 ) {
if ( ! source . metaWindow &&
( ! source . app || ! source . app . can _open _new _window ( ) ) &&
( source . app || ! source . shellWorkspaceLaunch ) &&
2025-02-09 23:16:18 +01:00
! ( source instanceof AppDisplay . FolderIcon ) )
2025-02-09 23:13:53 +01:00
return false ;
let isWindow = ! ! source . metaWindow ;
let newWorkspaceIndex ;
[ newWorkspaceIndex , this . _dropPlaceholderPos ] = [ this . _dropPlaceholderPos , - 1 ] ;
this . _spliceIndex = newWorkspaceIndex ;
Main . wm . insertWorkspace ( newWorkspaceIndex ) ;
if ( isWindow ) {
// Move the window to our monitor first if necessary.
let thumbMonitor = this . _thumbnails [ newWorkspaceIndex ] . monitorIndex ;
Main . moveWindowToMonitorAndWorkspace ( source . metaWindow ,
thumbMonitor , newWorkspaceIndex , true ) ;
} else if ( source . app && source . app . can _open _new _window ( ) ) {
if ( source . animateLaunchAtPos )
source . animateLaunchAtPos ( actor . x , actor . y ) ;
source . app . open _new _window ( newWorkspaceIndex ) ;
} else if ( ! source . app && source . shellWorkspaceLaunch ) {
// While unused in our own drag sources, shellWorkspaceLaunch allows
// extensions to define custom actions for their drag sources.
source . shellWorkspaceLaunch ( {
workspace : newWorkspaceIndex ,
timestamp : time ,
} ) ;
2025-02-09 23:16:18 +01:00
} else if ( source instanceof AppDisplay . FolderIcon ) {
for ( let app of source . view . _apps ) {
// const app = Shell.AppSystem.get_default().lookup_app(id);
app . open _new _window ( newWorkspaceIndex ) ;
2025-02-09 23:13:53 +01:00
}
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
if ( source . app || ( ! source . app && source . shellWorkspaceLaunch ) ) {
// This new workspace will be automatically removed if the application fails
// to open its first window within some time, as tracked by Shell.WindowTracker.
// Here, we only add a very brief timeout to avoid the _immediate_ removal of the
// workspace while we wait for the startup sequence to load.
let workspaceManager = global . workspace _manager ;
Main . wm . keepWorkspaceAlive ( workspaceManager . get _workspace _by _index ( newWorkspaceIndex ) ,
2025-02-09 23:16:18 +01:00
WORKSPACE _KEEP _ALIVE _TIME ) ;
2025-02-09 23:13:53 +01:00
}
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
// Start the animation on the workspace (which is actually
// an old one which just became empty)
let thumbnail = this . _thumbnails [ newWorkspaceIndex ] ;
this . _setThumbnailState ( thumbnail , ThumbnailState . NEW ) ;
thumbnail . slide _position = 1 ;
thumbnail . collapse _fraction = 1 ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
this . _queueUpdateStates ( ) ;
return true ;
} else {
return false ;
}
2025-02-09 23:09:13 +01:00
} ,
2025-02-09 23:13:53 +01:00
handleDragOver ( source , actor , x , y , time ) {
// switch axis for vertical orientation
if ( this . _boxOrientation )
x = y ;
2025-02-09 23:09:13 +01:00
if ( ! source . metaWindow &&
( ! source . app || ! source . app . can _open _new _window ( ) ) &&
( source . app || ! source . shellWorkspaceLaunch ) &&
2025-02-09 23:16:18 +01:00
source !== Main . xdndHandler && ! ( source instanceof AppDisplay . FolderIcon ) )
2025-02-09 23:09:13 +01:00
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 length = this . _thumbnails . length ;
for ( let i = 0 ; i < length ; i ++ ) {
const index = rtl ? length - i - 1 : i ;
if ( canCreateWorkspaces && source !== Main . xdndHandler ) {
const [ targetStart , targetEnd ] =
this . _getPlaceholderTarget ( index , spacing , rtl ) ;
2025-02-09 23:13:53 +01:00
if ( x > targetStart && x <= targetEnd ) {
2025-02-09 23:09:13 +01:00
placeholderPos = index ;
break ;
}
}
2025-02-09 23:13:53 +01:00
if ( this . _withinWorkspace ( x , index , rtl ) ) {
2025-02-09 23:09:13 +01:00
this . _dropWorkspace = index ;
break ;
}
}
2025-02-09 23:13:53 +01:00
if ( this . _dropPlaceholderPos !== placeholderPos ) {
2025-02-09 23:09:13 +01:00
this . _dropPlaceholderPos = placeholderPos ;
this . queue _relayout ( ) ;
}
2025-02-09 23:13:53 +01:00
if ( this . _dropWorkspace !== - 1 )
2025-02-09 23:09:13 +01:00
return this . _thumbnails [ this . _dropWorkspace ] . handleDragOverInternal ( source , actor , time ) ;
2025-02-09 23:13:53 +01:00
else if ( this . _dropPlaceholderPos !== - 1 )
2025-02-09 23:09:13 +01:00
return source . metaWindow ? DND . DragMotionResult . MOVE _DROP : DND . DragMotionResult . COPY _DROP ;
else
return DND . DragMotionResult . CONTINUE ;
} ,
2025-02-09 23:16:18 +01:00
_updateStates ( ) {
const controlsManager = Main . overview . _overview . controls ;
const { currentState } = controlsManager . _stateAdjustment . getStateTransitionParams ( ) ;
this . SLIDE _ANIMATION _TIME = 200 ;
this . RESCALE _ANIMATION _TIME = 200 ;
// remove rescale animation during this scale transition, it is redundant and delayed
if ( ( currentState < 2 && currentState > 1 ) || controlsManager . _searchController . searchActive )
this . RESCALE _ANIMATION _TIME = 0 ;
this . _updateStateId = 0 ;
// If we are animating the indicator, wait
if ( this . _animatingIndicator )
return ;
// Likewise if we are in the process of hiding
if ( ! this . _shouldShow && this . visible )
return ;
// Then slide out any thumbnails that have been destroyed
this . _iterateStateThumbnails ( ThumbnailState . REMOVING , thumbnail => {
this . _setThumbnailState ( thumbnail , ThumbnailState . ANIMATING _OUT ) ;
thumbnail . ease _property ( 'slide-position' , 1 , {
duration : this . SLIDE _ANIMATION _TIME ,
mode : Clutter . AnimationMode . LINEAR ,
onComplete : ( ) => {
this . _setThumbnailState ( thumbnail , ThumbnailState . ANIMATED _OUT ) ;
this . _queueUpdateStates ( ) ;
} ,
} ) ;
} ) ;
// As long as things are sliding out, don't proceed
if ( this . _stateCounts [ ThumbnailState . ANIMATING _OUT ] > 0 )
return ;
// Once that's complete, we can start scaling to the new size,
// collapse any removed thumbnails and expand added ones
this . _iterateStateThumbnails ( ThumbnailState . ANIMATED _OUT , thumbnail => {
this . _setThumbnailState ( thumbnail , ThumbnailState . COLLAPSING ) ;
thumbnail . ease _property ( 'collapse-fraction' , 1 , {
duration : this . RESCALE _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
onComplete : ( ) => {
this . _stateCounts [ thumbnail . state ] -- ;
thumbnail . state = ThumbnailState . DESTROYED ;
let index = this . _thumbnails . indexOf ( thumbnail ) ;
this . _thumbnails . splice ( index , 1 ) ;
thumbnail . destroy ( ) ;
this . _queueUpdateStates ( ) ;
} ,
} ) ;
} ) ;
this . _iterateStateThumbnails ( ThumbnailState . NEW , thumbnail => {
this . _setThumbnailState ( thumbnail , ThumbnailState . EXPANDING ) ;
thumbnail . ease _property ( 'collapse-fraction' , 0 , {
duration : this . SLIDE _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
onComplete : ( ) => {
this . _setThumbnailState ( thumbnail , ThumbnailState . EXPANDED ) ;
this . _queueUpdateStates ( ) ;
} ,
} ) ;
} ) ;
if ( this . _pendingScaleUpdate ) {
this . ease _property ( 'scale' , this . _targetScale , {
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
duration : this . RESCALE _ANIMATION _TIME ,
onComplete : ( ) => this . _queueUpdateStates ( ) ,
} ) ;
this . _queueUpdateStates ( ) ;
this . _pendingScaleUpdate = false ;
}
// Wait until that's done
if ( this . _scale !== this . _targetScale ||
this . _stateCounts [ ThumbnailState . COLLAPSING ] > 0 ||
this . _stateCounts [ ThumbnailState . EXPANDING ] > 0 )
return ;
// And then slide in any new thumbnails
this . _iterateStateThumbnails ( ThumbnailState . EXPANDED , thumbnail => {
this . _setThumbnailState ( thumbnail , ThumbnailState . ANIMATING _IN ) ;
thumbnail . ease _property ( 'slide-position' , 0 , {
duration : this . SLIDE _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
onComplete : ( ) => {
this . _setThumbnailState ( thumbnail , ThumbnailState . NORMAL ) ;
} ,
} ) ;
} ) ;
} ,
2025-02-09 23:13:53 +01:00
_getPlaceholderTarget ( ... args ) {
if ( this . _boxOrientation )
return ThumbnailsBoxVertical . _getPlaceholderTarget . bind ( this ) ( ... args ) ;
else
return ThumbnailsBoxHorizontal . _getPlaceholderTarget . bind ( this ) ( ... args ) ;
} ,
_withinWorkspace ( ... args ) {
if ( this . _boxOrientation )
return ThumbnailsBoxVertical . _withinWorkspace . bind ( this ) ( ... args ) ;
else
return ThumbnailsBoxHorizontal . _withinWorkspace . bind ( this ) ( ... args ) ;
} ,
2025-02-09 23:16:18 +01:00
vfunc _get _preferred _width ( ... args ) {
2025-02-09 23:13:53 +01:00
if ( this . _boxOrientation )
2025-02-09 23:16:18 +01:00
return ThumbnailsBoxVertical . vfunc _get _preferred _width . bind ( this ) ( ... args ) ;
2025-02-09 23:13:53 +01:00
else
2025-02-09 23:16:18 +01:00
return ThumbnailsBoxHorizontal . vfunc _get _preferred _width . bind ( this ) ( ... args ) ;
2025-02-09 23:13:53 +01:00
} ,
2025-02-09 23:16:18 +01:00
vfunc _get _preferred _height ( ... args ) {
2025-02-09 23:13:53 +01:00
if ( this . _boxOrientation )
2025-02-09 23:16:18 +01:00
return ThumbnailsBoxVertical . vfunc _get _preferred _height . bind ( this ) ( ... args ) ;
2025-02-09 23:13:53 +01:00
else
2025-02-09 23:16:18 +01:00
return ThumbnailsBoxHorizontal . vfunc _get _preferred _height . bind ( this ) ( ... args ) ;
2025-02-09 23:13:53 +01:00
} ,
vfunc _allocate ( ... args ) {
if ( this . _boxOrientation )
return ThumbnailsBoxVertical . vfunc _allocate . bind ( this ) ( ... args ) ;
else
return ThumbnailsBoxHorizontal . vfunc _allocate . bind ( this ) ( ... args ) ;
} ,
_updateShouldShow ( ... args ) {
if ( this . _boxOrientation )
return ThumbnailsBoxVertical . _updateShouldShow . bind ( this ) ( ... args ) ;
else
return ThumbnailsBoxHorizontal . _updateShouldShow . bind ( this ) ( ... args ) ;
} ,
} ;
const ThumbnailsBoxVertical = {
_getPlaceholderTarget ( index , spacing , rtl ) {
this . _dropPlaceholder . add _style _class _name ( 'placeholder-vertical' ) ;
const workspace = this . _thumbnails [ index ] ;
let targetY1 ;
let targetY2 ;
if ( rtl ) {
const baseY = workspace . y + workspace . height ;
targetY1 = baseY - WORKSPACE _CUT _SIZE ;
targetY2 = baseY + spacing + WORKSPACE _CUT _SIZE ;
} else {
targetY1 = workspace . y - spacing - WORKSPACE _CUT _SIZE ;
targetY2 = workspace . y + WORKSPACE _CUT _SIZE ;
}
if ( index === 0 ) {
if ( rtl )
targetY2 -= spacing + WORKSPACE _CUT _SIZE ;
else
targetY1 += spacing + WORKSPACE _CUT _SIZE ;
}
if ( index === this . _dropPlaceholderPos ) {
const placeholderHeight = this . _dropPlaceholder . get _height ( ) + spacing ;
if ( rtl )
targetY2 += placeholderHeight ;
else
targetY1 -= placeholderHeight ;
}
return [ targetY1 , targetY2 ] ;
} ,
_withinWorkspace ( y , index , rtl ) {
const length = this . _thumbnails . length ;
const workspace = this . _thumbnails [ index ] ;
let workspaceY1 = workspace . y + WORKSPACE _CUT _SIZE ;
let workspaceY2 = workspace . y + workspace . height - WORKSPACE _CUT _SIZE ;
if ( index === length - 1 ) {
if ( rtl )
workspaceY1 -= WORKSPACE _CUT _SIZE ;
else
workspaceY2 += WORKSPACE _CUT _SIZE ;
}
return y > workspaceY1 && y <= workspaceY2 ;
} ,
2025-02-09 23:16:18 +01:00
vfunc _get _preferred _width ( forHeight ) {
if ( forHeight < 10 )
return [ this . _porthole . width , this . _porthole . width ] ;
2025-02-09 23:09:13 +01:00
let themeNode = this . get _theme _node ( ) ;
forHeight = themeNode . adjust _for _width ( forHeight ) ;
let spacing = themeNode . get _length ( 'spacing' ) ;
let nWorkspaces = this . _thumbnails . length ;
let totalSpacing = ( nWorkspaces - 1 ) * spacing ;
const avail = forHeight - totalSpacing ;
let scale = ( avail / nWorkspaces ) / this . _porthole . height ;
const width = Math . round ( this . _porthole . width * scale ) ;
2025-02-09 23:13:53 +01:00
return themeNode . adjust _preferred _height ( width , width ) ;
2025-02-09 23:09:13 +01:00
} ,
2025-02-09 23:16:18 +01:00
vfunc _get _preferred _height ( forWidth ) {
if ( forWidth < 10 )
return [ 0 , this . _porthole . height ] ;
2025-02-09 23:09:13 +01:00
let themeNode = this . get _theme _node ( ) ;
let spacing = themeNode . get _length ( 'spacing' ) ;
let nWorkspaces = this . _thumbnails . length ;
2025-02-09 23:13:53 +01:00
// remove also top/bottom box padding
let totalSpacing = ( nWorkspaces - 3 ) * spacing ;
2025-02-09 23:09:13 +01:00
const ratio = this . _porthole . width / this . _porthole . height ;
2025-02-09 23:16:18 +01:00
const tmbHeight = themeNode . adjust _for _width ( forWidth ) / ratio ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
const naturalheight = this . _thumbnails . reduce ( ( accumulator , thumbnail /* , index*/ ) => {
2025-02-09 23:09:13 +01:00
const progress = 1 - thumbnail . collapse _fraction ;
2025-02-09 23:13:53 +01:00
const height = tmbHeight * progress ;
2025-02-09 23:09:13 +01:00
return accumulator + height ;
} , 0 ) ;
2025-02-09 23:16:18 +01:00
return themeNode . adjust _preferred _width ( totalSpacing , Math . round ( naturalheight ) ) ;
2025-02-09 23:09:13 +01:00
} ,
// removes extra space (extraWidth in the original function), we need the box as accurate as possible
// for precise app grid transition animation
2025-02-09 23:13:53 +01:00
vfunc _allocate ( box ) {
2025-02-09 23:09:13 +01:00
this . set _allocation ( box ) ;
2025-02-09 23:13:53 +01:00
let rtl = Clutter . get _default _text _direction ( ) === Clutter . TextDirection . RTL ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
if ( this . _thumbnails . length === 0 ) // not visible
2025-02-09 23:09:13 +01:00
return ;
let themeNode = this . get _theme _node ( ) ;
box = themeNode . get _content _box ( box ) ;
const portholeWidth = this . _porthole . width ;
const portholeHeight = this . _porthole . height ;
const spacing = themeNode . get _length ( 'spacing' ) ;
2025-02-09 23:13:53 +01:00
/* const nWorkspaces = this._thumbnails.length;*/
2025-02-09 23:09:13 +01:00
// Compute the scale we'll need once everything is updated,
// unless we are currently transitioning
if ( this . _expandFraction === 1 ) {
2025-02-09 23:13:53 +01:00
// remove size "breathing" during adding/removing workspaces
/ * c o n s t t o t a l S p a c i n g = ( n W o r k s p a c e s - 1 ) * s p a c i n g ;
const availableHeight = ( box . get _height ( ) - totalSpacing ) / nWorkspaces ; * /
2025-02-09 23:09:13 +01:00
const hScale = box . get _width ( ) / portholeWidth ;
2025-02-09 23:13:53 +01:00
/* const vScale = availableHeight / portholeHeight;*/
const vScale = box . get _height ( ) / portholeHeight ;
2025-02-09 23:09:13 +01:00
const newScale = Math . min ( hScale , vScale ) ;
if ( newScale !== this . _targetScale ) {
if ( this . _targetScale > 0 ) {
// We don't ease immediately because we need to observe the
// ordering in queueUpdateStates - if workspaces have been
// removed we need to slide them out as the first thing.
this . _targetScale = newScale ;
this . _pendingScaleUpdate = true ;
} else {
this . _targetScale = this . _scale = newScale ;
}
this . _queueUpdateStates ( ) ;
}
}
const ratio = portholeWidth / portholeHeight ;
const thumbnailFullHeight = Math . round ( portholeHeight * this . _scale ) ;
const thumbnailWidth = Math . round ( thumbnailFullHeight * ratio ) ;
const thumbnailHeight = thumbnailFullHeight * this . _expandFraction ;
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 indicatorThemeNode = this . _indicator . get _theme _node ( ) ;
let indicatorTopFullBorder = indicatorThemeNode . get _padding ( St . Side . TOP ) + indicatorThemeNode . get _border _width ( St . Side . TOP ) ;
let indicatorBottomFullBorder = indicatorThemeNode . get _padding ( St . Side . BOTTOM ) + indicatorThemeNode . get _border _width ( St . Side . BOTTOM ) ;
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 ;
2025-02-09 23:13:53 +01:00
if ( this . _dropPlaceholderPos === - 1 ) {
2025-02-09 23:09:13 +01:00
this . _dropPlaceholder . allocate _preferred _size (
... this . _dropPlaceholder . get _position ( ) ) ;
2025-02-09 23:16:18 +01:00
const laters = global . compositor . get _laters ( ) ;
laters . add ( Meta . LaterType . BEFORE _REDRAW , ( ) => {
this . _dropPlaceholder . hide ( ) ;
} ) ;
2025-02-09 23:09:13 +01:00
}
let childBox = new Clutter . ActorBox ( ) ;
for ( let i = 0 ; i < this . _thumbnails . length ; i ++ ) {
const thumbnail = this . _thumbnails [ i ] ;
if ( i > 0 )
y += spacing - Math . round ( thumbnail . collapse _fraction * spacing ) ;
const x1 = box . x1 ;
const x2 = x1 + thumbnailWidth ;
if ( i === this . _dropPlaceholderPos ) {
2025-02-09 23:13:53 +01:00
let [ , placeholderHeight ] = this . _dropPlaceholder . get _preferred _width ( - 1 ) ;
2025-02-09 23:09:13 +01:00
childBox . x1 = x1 ;
childBox . x2 = x2 ;
if ( rtl ) {
childBox . y2 = box . y2 - Math . round ( y ) ;
childBox . y1 = box . y2 - Math . round ( y + placeholderHeight ) ;
} else {
childBox . y1 = Math . round ( y ) ;
childBox . y2 = Math . round ( y + placeholderHeight ) ;
}
this . _dropPlaceholder . allocate ( childBox ) ;
2025-02-09 23:16:18 +01:00
const laters = global . compositor . get _laters ( ) ;
laters . add ( Meta . LaterType . BEFORE _REDRAW , ( ) => {
this . _dropPlaceholder . show ( ) ;
} ) ;
2025-02-09 23:09:13 +01:00
y += placeholderHeight + spacing ;
}
// 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.
const y1 = Math . round ( y ) ;
const y2 = Math . round ( y + thumbnailHeight ) ;
const roundedHScale = ( y2 - y1 ) / portholeHeight ;
// Allocating a scaled actor is funny - x1/y1 correspond to the origin
// of the actor, but x2/y2 are increased by the *unscaled* size.
if ( rtl ) {
childBox . y2 = box . y2 - y1 ;
childBox . y1 = box . y2 - ( y1 + thumbnailHeight ) ;
} else {
childBox . y1 = y1 ;
childBox . y2 = y1 + thumbnailHeight ;
}
childBox . x1 = x1 ;
childBox . x2 = x1 + thumbnailWidth ;
thumbnail . setScale ( roundedHScale , roundedVScale ) ;
thumbnail . allocate ( childBox ) ;
if ( i === indicatorUpperWs ) {
indicatorUpperY1 = childBox . y1 ;
indicatorUpperY2 = childBox . y2 ;
}
if ( i === indicatorLowerWs ) {
indicatorLowerY1 = childBox . y1 ;
indicatorLowerY2 = childBox . y2 ;
}
// 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 ) ;
}
childBox . x1 = box . x1 ;
childBox . x2 = box . x1 + thumbnailWidth ;
const indicatorY1 = indicatorLowerY1 +
( indicatorUpperY1 - indicatorLowerY1 ) * ( indicatorValue % 1 ) ;
const indicatorY2 = indicatorLowerY2 +
( indicatorUpperY2 - indicatorLowerY2 ) * ( indicatorValue % 1 ) ;
childBox . y1 = indicatorY1 - indicatorTopFullBorder ;
childBox . y2 = indicatorY2 + indicatorBottomFullBorder ;
childBox . x1 -= indicatorLeftFullBorder ;
childBox . x2 += indicatorRightFullBorder ;
this . _indicator . allocate ( childBox ) ;
} ,
2025-02-09 23:13:53 +01:00
_updateShouldShow ( ) {
2025-02-09 23:09:13 +01:00
const shouldShow = opt . SHOW _WS _TMB ;
if ( this . _shouldShow === shouldShow )
return ;
this . _shouldShow = shouldShow ;
this . notify ( 'should-show' ) ;
2025-02-09 23:13:53 +01:00
} ,
} ;
2025-02-09 23:09:13 +01:00
// ThumbnailsBox Horizontal
2025-02-09 23:13:53 +01:00
const ThumbnailsBoxHorizontal = {
_getPlaceholderTarget ( index , spacing , rtl ) {
const workspace = this . _thumbnails [ index ] ;
let targetX1 ;
let targetX2 ;
if ( rtl ) {
const baseX = workspace . x + workspace . width ;
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 ;
} ,
2025-02-09 23:16:18 +01:00
vfunc _get _preferred _height ( forWidth ) {
if ( forWidth < 10 )
return [ this . _porthole . height , this . _porthole . height ] ;
2025-02-09 23:13:53 +01:00
let themeNode = this . get _theme _node ( ) ;
forWidth = themeNode . adjust _for _width ( forWidth ) ;
let spacing = themeNode . get _length ( 'spacing' ) ;
let nWorkspaces = this . _thumbnails . length ;
let totalSpacing = ( nWorkspaces - 1 ) * spacing ;
const avail = forWidth - totalSpacing ;
let scale = ( avail / nWorkspaces ) / this . _porthole . width ;
const height = Math . round ( this . _porthole . height * scale ) ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:13:53 +01:00
return themeNode . adjust _preferred _height ( height , height ) ;
} ,
2025-02-09 23:16:18 +01:00
vfunc _get _preferred _width ( forHeight ) {
if ( forHeight < 10 )
return [ 0 , this . _porthole . width ] ;
2025-02-09 23:13:53 +01:00
2025-02-09 23:09:13 +01:00
let themeNode = this . get _theme _node ( ) ;
let spacing = themeNode . get _length ( 'spacing' ) ;
let nWorkspaces = this . _thumbnails . length ;
2025-02-09 23:13:53 +01:00
// remove also left/right box padding from the total spacing
let totalSpacing = ( nWorkspaces - 3 ) * spacing ;
2025-02-09 23:09:13 +01:00
const ratio = this . _porthole . height / this . _porthole . width ;
2025-02-09 23:16:18 +01:00
const tmbWidth = themeNode . adjust _for _height ( forHeight ) / ratio ;
2025-02-09 23:13:53 +01:00
const naturalWidth = this . _thumbnails . reduce ( ( accumulator , thumbnail ) => {
2025-02-09 23:09:13 +01:00
const progress = 1 - thumbnail . collapse _fraction ;
const width = tmbWidth * progress ;
return accumulator + width ;
} , 0 ) ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:09:13 +01:00
return themeNode . adjust _preferred _width ( totalSpacing , naturalWidth ) ;
} ,
vfunc _allocate ( box ) {
this . set _allocation ( box ) ;
2025-02-09 23:13:53 +01:00
let rtl = Clutter . get _default _text _direction ( ) === Clutter . TextDirection . RTL ;
2025-02-09 23:09:13 +01:00
2025-02-09 23:13:53 +01:00
if ( this . _thumbnails . length === 0 ) // not visible
2025-02-09 23:09:13 +01:00
return ;
let themeNode = this . get _theme _node ( ) ;
box = themeNode . get _content _box ( box ) ;
const portholeWidth = this . _porthole . width ;
const portholeHeight = this . _porthole . height ;
const spacing = themeNode . get _length ( 'spacing' ) ;
2025-02-09 23:13:53 +01:00
/* const nWorkspaces = this._thumbnails.length; */
2025-02-09 23:09:13 +01:00
// Compute the scale we'll need once everything is updated,
// unless we are currently transitioning
if ( this . _expandFraction === 1 ) {
2025-02-09 23:13:53 +01:00
// remove size "breathing" during adding/removing workspaces
/ * c o n s t t o t a l S p a c i n g = ( n W o r k s p a c e s - 1 ) * s p a c i n g ;
2025-02-09 23:09:13 +01:00
const availableWidth = ( box . get _width ( ) - totalSpacing ) / nWorkspaces ;
2025-02-09 23:13:53 +01:00
const hScale = availableWidth / portholeWidth ; * /
const hScale = box . get _width ( ) / portholeWidth ;
2025-02-09 23:09:13 +01:00
const vScale = box . get _height ( ) / portholeHeight ;
const newScale = Math . min ( hScale , vScale ) ;
if ( newScale !== this . _targetScale ) {
if ( this . _targetScale > 0 ) {
// We don't ease immediately because we need to observe the
// ordering in queueUpdateStates - if workspaces have been
// removed we need to slide them out as the first thing.
this . _targetScale = newScale ;
this . _pendingScaleUpdate = true ;
} else {
this . _targetScale = this . _scale = newScale ;
}
this . _queueUpdateStates ( ) ;
}
}
const ratio = portholeWidth / portholeHeight ;
const thumbnailFullHeight = Math . round ( portholeHeight * this . _scale ) ;
const thumbnailWidth = Math . round ( thumbnailFullHeight * ratio ) ;
const thumbnailHeight = thumbnailFullHeight * this . _expandFraction ;
const roundedVScale = thumbnailHeight / portholeHeight ;
let indicatorValue = this . _scrollAdjustment . value ;
let indicatorUpperWs = Math . ceil ( indicatorValue ) ;
let indicatorLowerWs = Math . floor ( indicatorValue ) ;
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 ) ;
let indicatorBottomFullBorder = indicatorThemeNode . get _padding ( St . Side . BOTTOM ) + indicatorThemeNode . get _border _width ( St . Side . BOTTOM ) ;
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 x = box . x1 ;
2025-02-09 23:13:53 +01:00
if ( this . _dropPlaceholderPos === - 1 ) {
2025-02-09 23:09:13 +01:00
this . _dropPlaceholder . allocate _preferred _size (
... this . _dropPlaceholder . get _position ( ) ) ;
2025-02-09 23:16:18 +01:00
const laters = global . compositor . get _laters ( ) ;
laters . add ( Meta . LaterType . BEFORE _REDRAW , ( ) => {
this . _dropPlaceholder . hide ( ) ;
} ) ;
2025-02-09 23:09:13 +01:00
}
let childBox = new Clutter . ActorBox ( ) ;
for ( let i = 0 ; i < this . _thumbnails . length ; i ++ ) {
const thumbnail = this . _thumbnails [ i ] ;
if ( i > 0 )
x += spacing - Math . round ( thumbnail . collapse _fraction * spacing ) ;
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 ) ;
}
this . _dropPlaceholder . allocate ( childBox ) ;
2025-02-09 23:16:18 +01:00
const laters = global . compositor . get _laters ( ) ;
laters . add ( Meta . LaterType . BEFORE _REDRAW , ( ) => {
this . _dropPlaceholder . show ( ) ;
} ) ;
2025-02-09 23:09:13 +01:00
x += placeholderWidth + spacing ;
}
// 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.
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.
if ( rtl ) {
childBox . x2 = box . x2 - x1 ;
childBox . x1 = box . x2 - ( x1 + thumbnailWidth ) ;
} else {
childBox . x1 = x1 ;
childBox . x2 = x1 + thumbnailWidth ;
}
childBox . y1 = y1 ;
childBox . y2 = y1 + thumbnailHeight ;
thumbnail . setScale ( 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
x += thumbnailWidth - Math . round ( thumbnailWidth * thumbnail . collapse _fraction ) ;
}
childBox . y1 = box . y1 ;
childBox . y2 = box . y1 + thumbnailHeight ;
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 ) ;
} ,
2025-02-09 23:13:53 +01:00
_updateShouldShow : ThumbnailsBoxVertical . _updateShouldShow ,
} ;