@@ -29,6 +29,7 @@ const ICON_OPACITY = Math.round(255 * 0.9);
2929const ICON_SIZE = 128 ;
3030const ICON_OFFSET = - 5 ;
3131
32+ const WORKSPACE_DRAG_ANIMATION_TIME = 200 ;
3233const DRAGGING_WINDOW_OPACITY = Math . round ( 255 * 0.8 ) ;
3334const WINDOW_DND_SIZE = 256 ;
3435
@@ -321,6 +322,34 @@ var ExpoWorkspaceThumbnail = GObject.registerClass({
321322
322323 this . _lastButtonPressTimeStamp = 0 ;
323324
325+ // Connected before makeDraggable so this handler fires first. If the
326+ // click originated from a window clone, inhibit workspace drag so the
327+ // window clone's own DnD handles it instead.
328+ this . connect ( 'button-press-event' , ( actor , event ) => {
329+ if ( ! this . windows )
330+ return Clutter . EVENT_PROPAGATE ;
331+ let source = event . get_source ( ) ;
332+ this . _draggable . inhibit = this . windows . some (
333+ w => w === source || w . contains ( source )
334+ ) ;
335+ return Clutter . EVENT_PROPAGATE ;
336+ } ) ;
337+
338+ this . _draggable = DND . makeDraggable ( this ) ;
339+ this . _draggable . connect ( 'drag-begin' , ( ) => {
340+ this . inWorkspaceDrag = true ;
341+ } ) ;
342+ this . _draggable . connect ( 'drag-end' , ( ) => {
343+ this . inWorkspaceDrag = false ;
344+ this . show ( ) ;
345+ this . frame . show ( ) ;
346+ this . title . show ( ) ;
347+ this . box . _endWorkspaceDrag ( ) ;
348+ // Reset so the next motion-event after drop re-runs the hover logic
349+ // (button + highlight).
350+ this . hovering = false ;
351+ } ) ;
352+
324353 this . title = new St . Entry ( { style_class : 'expo-workspaces-name-entry' ,
325354 track_hover : true ,
326355 can_focus : true } ) ;
@@ -938,8 +967,36 @@ var ExpoWorkspaceThumbnail = GObject.registerClass({
938967 return indexOne - 1 ;
939968 }
940969
941- // Draggable target interface
970+ getDragActor ( ) {
971+ let clone = new Clutter . Clone ( { source : this } ) ;
972+ clone . set_size ( this . width * this . scale_x ,
973+ this . height * this . scale_y ) ;
974+
975+ this . hide ( ) ;
976+ this . frame . hide ( ) ;
977+ this . title . hide ( ) ;
978+ this . box . _startWorkspaceDrag ( this . box . thumbnails . indexOf ( this ) ) ;
979+
980+ return clone ;
981+ }
982+
983+ getDragActorSource ( ) {
984+ return this ;
985+ }
986+
942987 handleDragOver ( source , actor , x , y , time ) {
988+ if ( source instanceof ExpoWorkspaceThumbnail ) {
989+ if ( source === this )
990+ return DND . DragMotionResult . CONTINUE ;
991+
992+ let alloc = this . get_allocation_box ( ) ;
993+ let scale = this . get_scale ( ) [ 0 ] ;
994+ let boxX = alloc . x1 + x * scale ;
995+ let boxY = alloc . y1 + y * scale ;
996+ this . box . _updateWorkspaceDragFromPosition ( boxX , boxY ) ;
997+ return DND . DragMotionResult . MOVE_DROP ;
998+ }
999+
9431000 this . emit ( 'drag-over' ) ;
9441001 if ( ! this . overviewMode ) {
9451002 this . overviewModeOn ( ) ;
@@ -1011,6 +1068,16 @@ var ExpoWorkspaceThumbnail = GObject.registerClass({
10111068 }
10121069
10131070 acceptDrop ( source , actor , x , y , time ) {
1071+ if ( source instanceof ExpoWorkspaceThumbnail ) {
1072+ let sourceIndex = this . box . _wsSourceIndex ;
1073+ let dropIndex = this . box . _wsDropIndex ;
1074+ this . box . _endWorkspaceDrag ( ) ;
1075+ if ( sourceIndex >= 0 && dropIndex >= 0 && sourceIndex !== dropIndex ) {
1076+ this . box . _reorderWorkspace ( sourceIndex , dropIndex ) ;
1077+ }
1078+ return true ;
1079+ }
1080+
10141081 if ( this . handleDragOverOrDrop ( false , source , actor , x , y , time ) != DND . DragMotionResult . CONTINUE ) {
10151082 if ( this . handleDragOverOrDrop ( true , source , actor , x , y , time ) != DND . DragMotionResult . CONTINUE ) {
10161083 this . restack ( true ) ;
@@ -1056,12 +1123,27 @@ var ExpoThumbnailsBox = GObject.registerClass({
10561123 // for the border and padding of the background actor.
10571124 this . background = new St . Bin ( { reactive :true } ) ;
10581125 this . add_child ( this . background ) ;
1059- this . background . handleDragOver = function ( source , actor , x , y , time ) {
1126+ this . background . handleDragOver = ( source , actor , x , y , time ) => {
1127+ if ( source instanceof ExpoWorkspaceThumbnail ) {
1128+ if ( this . _wsDropIndex < 0 )
1129+ return DND . DragMotionResult . CONTINUE ;
1130+ this . _updateWorkspaceDragFromPosition ( x , y ) ;
1131+ return DND . DragMotionResult . MOVE_DROP ;
1132+ }
10601133 return source . metaWindow && ! source . metaWindow . is_on_all_workspaces ( ) ?
10611134 DND . DragMotionResult . MOVE_DROP : DND . DragMotionResult . CONTINUE ;
10621135 } ;
10631136 this . background . acceptDrop = ( source , actor , x , y , time ) => {
1064- if ( this . background . handleDragOver ( source , actor , x , y , time ) === DND . DragMotionResult . MOVE_DROP ) {
1137+ if ( source instanceof ExpoWorkspaceThumbnail ) {
1138+ let sourceIndex = this . _wsSourceIndex ;
1139+ let dropIndex = this . _wsDropIndex ;
1140+ this . _endWorkspaceDrag ( ) ;
1141+ if ( sourceIndex >= 0 && dropIndex >= 0 && sourceIndex !== dropIndex ) {
1142+ this . _reorderWorkspace ( sourceIndex , dropIndex ) ;
1143+ }
1144+ return true ;
1145+ }
1146+ if ( this . background . handleDragOver ( source , actor , x , y , time ) === DND . DragMotionResult . MOVE_DROP ) {
10651147 let draggable = source . _draggable ;
10661148 actor . get_parent ( ) . remove_actor ( actor ) ;
10671149 draggable . _dragOrigParent . add_actor ( actor ) ;
@@ -1084,6 +1166,9 @@ var ExpoThumbnailsBox = GObject.registerClass({
10841166 this . targetScale = 0 ;
10851167 this . pendingScaleUpdate = false ;
10861168 this . stateUpdateQueued = false ;
1169+ this . _wsSourceIndex = - 1 ;
1170+ this . _wsDropIndex = - 1 ;
1171+ this . _slotCenters = [ ] ;
10871172
10881173 this . stateCounts = { } ;
10891174 for ( let key in ThumbnailState )
@@ -1163,6 +1248,14 @@ var ExpoThumbnailsBox = GObject.registerClass({
11631248 this . addThumbnails ( index , 1 ) ;
11641249 } ,
11651250 'workspace-removed' , ( ) => {
1251+ if ( this . _wsSourceIndex >= 0 ) {
1252+ let source = this . thumbnails [ this . _wsSourceIndex ] ;
1253+ if ( source && source . _draggable && source . _draggable . _dragInProgress )
1254+ source . _draggable . _cancelDrag ( null ) ;
1255+ else
1256+ this . _endWorkspaceDrag ( ) ;
1257+ }
1258+
11661259 this . button . hide ( ) ;
11671260
11681261 let removedCount = 0 ;
@@ -1227,6 +1320,70 @@ var ExpoThumbnailsBox = GObject.registerClass({
12271320 this . thumbnails [ this . kbThumbnailIndex ] . removeWorkspace ( ) ;
12281321 }
12291322
1323+ _startWorkspaceDrag ( sourceIndex ) {
1324+ this . _wsSourceIndex = sourceIndex ;
1325+ this . _wsDropIndex = sourceIndex ;
1326+ this . button . hide ( ) ;
1327+ this . queue_relayout ( ) ;
1328+ }
1329+
1330+ _updateWorkspaceDragFromPosition ( boxX , boxY ) {
1331+ if ( ! this . _slotCenters || this . _slotCenters . length === 0 )
1332+ return ;
1333+
1334+ let minDist = Infinity ;
1335+ let dropIndex = this . _wsDropIndex ;
1336+ for ( let i = 0 ; i < this . _slotCenters . length ; i ++ ) {
1337+ let dx = boxX - this . _slotCenters [ i ] . x ;
1338+ let dy = boxY - this . _slotCenters [ i ] . y ;
1339+ let dist = dx * dx + dy * dy ;
1340+ if ( dist < minDist ) {
1341+ minDist = dist ;
1342+ dropIndex = i ;
1343+ }
1344+ }
1345+
1346+ if ( this . _wsDropIndex !== dropIndex ) {
1347+ this . _wsDropIndex = dropIndex ;
1348+ this . queue_relayout ( ) ;
1349+ }
1350+ }
1351+
1352+ _endWorkspaceDrag ( ) {
1353+ this . _wsSourceIndex = - 1 ;
1354+ this . _wsDropIndex = - 1 ;
1355+ this . queue_relayout ( ) ;
1356+ }
1357+
1358+ _reorderWorkspace ( oldIndex , newIndex ) {
1359+ Main . reorderWorkspace ( oldIndex , newIndex ) ;
1360+
1361+ let thumbnail = this . thumbnails . splice ( oldIndex , 1 ) [ 0 ] ;
1362+ this . thumbnails . splice ( newIndex , 0 , thumbnail ) ;
1363+
1364+ if ( this . kbThumbnailIndex === oldIndex ) {
1365+ this . kbThumbnailIndex = newIndex ;
1366+ } else if ( oldIndex < this . kbThumbnailIndex && newIndex >= this . kbThumbnailIndex ) {
1367+ this . kbThumbnailIndex -- ;
1368+ } else if ( oldIndex > this . kbThumbnailIndex && newIndex <= this . kbThumbnailIndex ) {
1369+ this . kbThumbnailIndex ++ ;
1370+ }
1371+
1372+ for ( let i = 0 ; i < this . thumbnails . length ; i ++ ) {
1373+ this . thumbnails [ i ] . refreshTitle ( ) ;
1374+ }
1375+
1376+ let activeWorkspace = global . workspace_manager . get_active_workspace ( ) ;
1377+ for ( let i = 0 ; i < this . thumbnails . length ; i ++ ) {
1378+ let isActive = this . thumbnails [ i ] . metaWorkspace === activeWorkspace ;
1379+ this . thumbnails [ i ] . setActive ( isActive ) ;
1380+ if ( isActive )
1381+ this . lastActiveWorkspace = this . thumbnails [ i ] ;
1382+ }
1383+
1384+ this . queue_relayout ( ) ;
1385+ }
1386+
12301387 // returns true if symbol was understood, false otherwise
12311388 selectNextWorkspace ( symbol ) {
12321389 let prevIndex = this . kbThumbnailIndex ;
@@ -1654,37 +1811,71 @@ var ExpoThumbnailsBox = GObject.registerClass({
16541811
16551812 this . background . allocate ( childBox , flags ) ;
16561813
1814+ // During a workspace drag, build a virtual display order:
1815+ // remove the source thumbnail and leave a gap at the drop position.
1816+ let displayOrder = [ ] ;
1817+ let isDragging = this . _wsSourceIndex >= 0 && this . _wsDropIndex >= 0 ;
1818+ if ( isDragging ) {
1819+ for ( let i = 0 ; i < this . thumbnails . length ; i ++ ) {
1820+ if ( i !== this . _wsSourceIndex )
1821+ displayOrder . push ( this . thumbnails [ i ] ) ;
1822+ }
1823+ displayOrder . splice ( this . _wsDropIndex , 0 , null ) ;
1824+ this . _slotCenters = [ ] ;
1825+ } else {
1826+ for ( let i = 0 ; i < this . thumbnails . length ; i ++ )
1827+ displayOrder . push ( this . thumbnails [ i ] ) ;
1828+ }
1829+
16571830 let x ;
16581831 let y = spacing + Math . floor ( ( availY - nRows * thumbnailHeight ) / 2 ) ;
1659- for ( let i = 0 ; i < this . thumbnails . length ; i ++ ) {
1832+ for ( let i = 0 ; i < displayOrder . length ; i ++ ) {
16601833 let column = i % nColumns ;
16611834 let row = Math . floor ( i / nColumns ) ;
1662- let cItemsInRow = Math . min ( this . thumbnails . length - ( row * nColumns ) , nColumns ) ;
1835+ let cItemsInRow = Math . min ( displayOrder . length - ( row * nColumns ) , nColumns ) ;
16631836 x = column > 0 ? x : calcPaddingX ( cItemsInRow ) ;
1664- let rowMultiplier = row + 1 ;
16651837
1666- let thumbnail = this . thumbnails [ i ] ;
1838+ if ( isDragging ) {
1839+ this . _slotCenters . push ( {
1840+ x : x + thumbnailWidth / 2 ,
1841+ y : y + thumbnailHeight / 2
1842+ } ) ;
1843+ }
1844+
1845+ let thumbnail = displayOrder [ i ] ;
1846+
1847+ if ( thumbnail === null ) {
1848+ x += thumbnailWidth + spacing ;
1849+ y += ( i + 1 ) % nColumns > 0 ? 0 : thumbnailHeight + extraHeight + thTitleMargin ;
1850+ continue ;
1851+ }
16671852
16681853 // We might end up with thumbnailHeight being something like 99.33
16691854 // pixels. To make this work and not end up with a gap at the bottom,
16701855 // we need some thumbnails to be 99 pixels and some 100 pixels height;
16711856 // we compute an actual scale separately for each thumbnail.
16721857 let x1 = Math . round ( x + ( thumbnailWidth * thumbnail . slide_position / 2 ) ) ;
16731858 let x2 = Math . round ( x + thumbnailWidth ) ;
1859+ let y1 = y ;
1860+ let y2 = y1 + thumbnailHeight ;
16741861
1675- let y1 , y2 ;
1862+ let scale = this . thumbnail_scale * ( 1 - thumbnail . slide_position ) ;
16761863
1677- y1 = y ;
1678- y2 = y1 + thumbnailHeight ;
1864+ let animate = isDragging && thumbnail . has_allocation ( ) ;
1865+ if ( animate ) {
1866+ for ( let actor of [ thumbnail , thumbnail . frame , thumbnail . title ] ) {
1867+ actor . save_easing_state ( ) ;
1868+ actor . set_easing_mode ( Clutter . AnimationMode . EASE_OUT_QUAD ) ;
1869+ actor . set_easing_duration ( WORKSPACE_DRAG_ANIMATION_TIME ) ;
1870+ }
1871+ }
16791872
16801873 // Allocating a scaled actor is funny - x1/y1 correspond to the origin
16811874 // of the actor, but x2/y2 are increased by the *unscaled* size.
16821875 childBox . x1 = x1 ;
16831876 childBox . x2 = x1 + portholeWidth ;
16841877 childBox . y1 = y1 ;
16851878 childBox . y2 = y1 + portholeHeight ;
1686-
1687- let scale = this . thumbnail_scale * ( 1 - thumbnail . slide_position ) ;
16881879 thumbnail . set_scale ( scale , scale ) ;
16891880 thumbnail . allocate ( childBox , flags ) ;
16901881
@@ -1704,6 +1895,12 @@ var ExpoThumbnailsBox = GObject.registerClass({
17041895 childBox . y2 = childBox . y1 + thumbnail . title . height ;
17051896 thumbnail . title . allocate ( childBox , flags ) ;
17061897
1898+ if ( animate ) {
1899+ for ( let actor of [ thumbnail , thumbnail . frame , thumbnail . title ] ) {
1900+ actor . restore_easing_state ( ) ;
1901+ }
1902+ }
1903+
17071904 x += thumbnailWidth + spacing ;
17081905 y += ( i + 1 ) % nColumns > 0 ? 0 : thumbnailHeight + extraHeight + thTitleMargin ;
17091906 }
0 commit comments