@@ -26,7 +26,7 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen
26
26
import { ScrollbarVisibility } from 'vs/base/common/scrollable' ;
27
27
import { getOrSet } from 'vs/base/common/map' ;
28
28
import { IThemeService , registerThemingParticipant } from 'vs/platform/theme/common/themeService' ;
29
- import { TAB_INACTIVE_BACKGROUND , TAB_ACTIVE_BACKGROUND , TAB_ACTIVE_FOREGROUND , TAB_INACTIVE_FOREGROUND , TAB_BORDER , EDITOR_DRAG_AND_DROP_BACKGROUND , TAB_UNFOCUSED_ACTIVE_FOREGROUND , TAB_UNFOCUSED_INACTIVE_FOREGROUND , TAB_UNFOCUSED_ACTIVE_BACKGROUND , TAB_UNFOCUSED_ACTIVE_BORDER , TAB_ACTIVE_BORDER , TAB_HOVER_BACKGROUND , TAB_HOVER_BORDER , TAB_UNFOCUSED_HOVER_BACKGROUND , TAB_UNFOCUSED_HOVER_BORDER , EDITOR_GROUP_HEADER_TABS_BACKGROUND , WORKBENCH_BACKGROUND , TAB_ACTIVE_BORDER_TOP , TAB_UNFOCUSED_ACTIVE_BORDER_TOP , TAB_ACTIVE_MODIFIED_BORDER , TAB_INACTIVE_MODIFIED_BORDER , TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER , TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER , TAB_UNFOCUSED_INACTIVE_BACKGROUND , TAB_HOVER_FOREGROUND , TAB_UNFOCUSED_HOVER_FOREGROUND , EDITOR_GROUP_HEADER_TABS_BORDER , TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme' ;
29
+ import { TAB_INACTIVE_BACKGROUND , TAB_ACTIVE_BACKGROUND , TAB_ACTIVE_FOREGROUND , TAB_INACTIVE_FOREGROUND , TAB_BORDER , EDITOR_DRAG_AND_DROP_BACKGROUND , TAB_UNFOCUSED_ACTIVE_FOREGROUND , TAB_UNFOCUSED_INACTIVE_FOREGROUND , TAB_UNFOCUSED_ACTIVE_BACKGROUND , TAB_UNFOCUSED_ACTIVE_BORDER , TAB_ACTIVE_BORDER , TAB_HOVER_BACKGROUND , TAB_HOVER_BORDER , TAB_UNFOCUSED_HOVER_BACKGROUND , TAB_UNFOCUSED_HOVER_BORDER , EDITOR_GROUP_HEADER_TABS_BACKGROUND , WORKBENCH_BACKGROUND , TAB_ACTIVE_BORDER_TOP , TAB_UNFOCUSED_ACTIVE_BORDER_TOP , TAB_ACTIVE_MODIFIED_BORDER , TAB_INACTIVE_MODIFIED_BORDER , TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER , TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER , TAB_UNFOCUSED_INACTIVE_BACKGROUND , TAB_HOVER_FOREGROUND , TAB_UNFOCUSED_HOVER_FOREGROUND , EDITOR_GROUP_HEADER_TABS_BORDER , TAB_LAST_PINNED_BORDER , TAB_Drag_And_Drop_Between_Indicator } from 'vs/workbench/common/theme' ;
30
30
import { activeContrastBorder , contrastBorder , editorBackground } from 'vs/platform/theme/common/colorRegistry' ;
31
31
import { ResourcesDropHandler , DraggedEditorIdentifier , DraggedEditorGroupIdentifier , extractTreeDropData , isWindowDraggedOver } from 'vs/workbench/browser/dnd' ;
32
32
import { Color } from 'vs/base/common/color' ;
@@ -132,6 +132,29 @@ export class MultiEditorTabsControl extends EditorTabsControl {
132
132
private lastMouseWheelEventTime = 0 ;
133
133
private isMouseOverTabs = false ;
134
134
135
+ private _dndDropTarget : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined ;
136
+ private set dndDropTarget ( target : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined ) {
137
+ const oldTargets = this . _dndDropTarget ;
138
+ if ( oldTargets === target || oldTargets && target && oldTargets . leftElement === target . leftElement && oldTargets . rightElement === target . rightElement ) {
139
+ return ;
140
+ }
141
+
142
+ const dropClassLeft = 'drop-target-left' ;
143
+ const dropClassRight = 'drop-target-right' ;
144
+
145
+ if ( oldTargets ) {
146
+ oldTargets . leftElement ?. classList . remove ( dropClassLeft ) ;
147
+ oldTargets . rightElement ?. classList . remove ( dropClassRight ) ;
148
+ }
149
+
150
+ if ( target ) {
151
+ target . leftElement ?. classList . add ( dropClassLeft ) ;
152
+ target . rightElement ?. classList . add ( dropClassRight ) ;
153
+ }
154
+
155
+ this . _dndDropTarget = target ;
156
+ }
157
+
135
158
constructor (
136
159
parent : HTMLElement ,
137
160
editorPartsView : IEditorPartsView ,
@@ -335,7 +358,6 @@ export class MultiEditorTabsControl extends EditorTabsControl {
335
358
336
359
// Return if the target is not on the tabs container
337
360
if ( e . target !== tabsContainer ) {
338
- this . updateDropFeedback ( tabsContainer , false ) ; // fixes https://github.com/microsoft/vscode/issues/52093
339
361
return ;
340
362
}
341
363
@@ -352,18 +374,6 @@ export class MultiEditorTabsControl extends EditorTabsControl {
352
374
let isLocalDragAndDrop = false ;
353
375
if ( this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
354
376
isLocalDragAndDrop = true ;
355
-
356
- const data = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
357
- if ( Array . isArray ( data ) ) {
358
- const localDraggedEditor = data [ 0 ] . identifier ;
359
- if ( this . groupView . id === localDraggedEditor . groupId && this . tabsModel . isLast ( localDraggedEditor . editor ) ) {
360
- if ( e . dataTransfer ) {
361
- e . dataTransfer . dropEffect = 'none' ;
362
- }
363
-
364
- return ;
365
- }
366
- }
367
377
}
368
378
369
379
// Update the dropEffect to "copy" if there is no local data to be dragged because
@@ -374,23 +384,23 @@ export class MultiEditorTabsControl extends EditorTabsControl {
374
384
}
375
385
}
376
386
377
- this . updateDropFeedback ( tabsContainer , true ) ;
387
+ this . updateDropFeedback ( tabsContainer , true , e ) ;
378
388
} ,
379
389
380
390
onDragLeave : e => {
381
- this . updateDropFeedback ( tabsContainer , false ) ;
391
+ this . updateDropFeedback ( tabsContainer , false , e ) ;
382
392
tabsContainer . classList . remove ( 'scroll' ) ;
383
393
} ,
384
394
385
395
onDragEnd : e => {
386
- this . updateDropFeedback ( tabsContainer , false ) ;
396
+ this . updateDropFeedback ( tabsContainer , false , e ) ;
387
397
tabsContainer . classList . remove ( 'scroll' ) ;
388
398
389
399
this . onGroupDragEnd ( e , lastDragEvent , tabsContainer , isNewWindowOperation ) ;
390
400
} ,
391
401
392
402
onDrop : e => {
393
- this . updateDropFeedback ( tabsContainer , false ) ;
403
+ this . updateDropFeedback ( tabsContainer , false , e ) ;
394
404
tabsContainer . classList . remove ( 'scroll' ) ;
395
405
396
406
if ( e . target === tabsContainer ) {
@@ -1038,25 +1048,22 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1038
1048
1039
1049
if ( e . dataTransfer ) {
1040
1050
e . dataTransfer . effectAllowed = 'copyMove' ;
1051
+ e . dataTransfer . setDragImage ( tab , 0 , 0 ) ; // top left corner of dragged tab set to cursor position
1041
1052
}
1042
1053
1043
1054
// Apply some datatransfer types to allow for dragging the element outside of the application
1044
1055
this . doFillResourceDataTransfers ( [ editor ] , e , isNewWindowOperation ) ;
1045
1056
1046
1057
// Fixes https://github.com/microsoft/vscode/issues/18733
1047
- tab . classList . add ( 'dragged' ) ;
1048
- scheduleAtNextAnimationFrame ( getWindow ( this . parent ) , ( ) => tab . classList . remove ( 'dragged' ) ) ;
1058
+ this . updateDropFeedback ( tab . cloneNode ( true ) as HTMLElement , true , e , tabIndex ) ;
1059
+ scheduleAtNextAnimationFrame ( getWindow ( this . parent ) , ( ) => this . updateDropFeedback ( tab , false , e , tabIndex ) ) ;
1049
1060
} ,
1050
1061
1051
1062
onDrag : e => {
1052
1063
lastDragEvent = e ;
1053
1064
} ,
1054
1065
1055
1066
onDragEnter : e => {
1056
-
1057
- // Update class to signal drag operation
1058
- tab . classList . add ( 'dragged-over' ) ;
1059
-
1060
1067
// Return if transfer is unsupported
1061
1068
if ( ! this . isSupportedDropTransfer ( e ) ) {
1062
1069
if ( e . dataTransfer ) {
@@ -1066,22 +1073,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1066
1073
return ;
1067
1074
}
1068
1075
1069
- // Return if dragged editor is the current tab dragged over
1070
1076
let isLocalDragAndDrop = false ;
1071
1077
if ( this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
1072
1078
isLocalDragAndDrop = true ;
1073
-
1074
- const data = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
1075
- if ( Array . isArray ( data ) ) {
1076
- const localDraggedEditor = data [ 0 ] . identifier ;
1077
- if ( localDraggedEditor . editor === this . tabsModel . getEditorByIndex ( tabIndex ) && localDraggedEditor . groupId === this . groupView . id ) {
1078
- if ( e . dataTransfer ) {
1079
- e . dataTransfer . dropEffect = 'none' ;
1080
- }
1081
-
1082
- return ;
1083
- }
1084
- }
1085
1079
}
1086
1080
1087
1081
// Update the dropEffect to "copy" if there is no local data to be dragged because
@@ -1092,26 +1086,24 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1092
1086
}
1093
1087
}
1094
1088
1095
- this . updateDropFeedback ( tab , true , tabIndex ) ;
1089
+ this . updateDropFeedback ( tab , true , e , tabIndex ) ;
1096
1090
} ,
1097
1091
1098
- onDragOver : ( _ , dragDuration ) => {
1092
+ onDragOver : ( e , dragDuration ) => {
1099
1093
if ( dragDuration >= MultiEditorTabsControl . DRAG_OVER_OPEN_TAB_THRESHOLD ) {
1100
1094
const draggedOverTab = this . tabsModel . getEditorByIndex ( tabIndex ) ;
1101
1095
if ( draggedOverTab && this . tabsModel . activeEditor !== draggedOverTab ) {
1102
1096
this . groupView . openEditor ( draggedOverTab , { preserveFocus : true } ) ;
1103
1097
}
1104
1098
}
1105
- } ,
1106
1099
1107
- onDragLeave : ( ) => {
1108
- tab . classList . remove ( 'dragged-over' ) ;
1109
- this . updateDropFeedback ( tab , false , tabIndex ) ;
1100
+ if ( e . dataTransfer ?. dropEffect !== 'none' ) {
1101
+ this . updateDropFeedback ( tab , true , e , tabIndex ) ;
1102
+ }
1110
1103
} ,
1111
1104
1112
1105
onDragEnd : async e => {
1113
- tab . classList . remove ( 'dragged-over' ) ;
1114
- this . updateDropFeedback ( tab , false , tabIndex ) ;
1106
+ this . updateDropFeedback ( tab , false , e , tabIndex ) ;
1115
1107
1116
1108
this . editorTransfer . clearData ( DraggedEditorIdentifier . prototype ) ;
1117
1109
@@ -1140,10 +1132,29 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1140
1132
} ,
1141
1133
1142
1134
onDrop : e => {
1143
- tab . classList . remove ( 'dragged-over' ) ;
1144
- this . updateDropFeedback ( tab , false , tabIndex ) ;
1135
+ this . updateDropFeedback ( tab , false , e , tabIndex ) ;
1136
+
1137
+ // compute the target index
1138
+ let targetIndex = tabIndex ;
1139
+ if ( ! this . isHeadOfTab ( e , tab ) ) {
1140
+ targetIndex ++ ;
1141
+ }
1142
+
1143
+ const editorIdentifiers = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
1144
+ if ( editorIdentifiers !== undefined ) {
1145
1145
1146
- this . onDrop ( e , tabIndex , tabsContainer ) ;
1146
+ const draggedEditorIdentifier = editorIdentifiers [ 0 ] . identifier ;
1147
+ const sourceGroup = this . editorPartsView . getGroup ( draggedEditorIdentifier . groupId ) ;
1148
+ if ( sourceGroup ?. id === this . groupView . id ) {
1149
+
1150
+ const editorIndex = sourceGroup . getIndexOfEditor ( draggedEditorIdentifier . editor ) ;
1151
+ if ( editorIndex < targetIndex ) {
1152
+ targetIndex -- ;
1153
+ }
1154
+ }
1155
+ }
1156
+
1157
+ this . onDrop ( e , targetIndex , tabsContainer ) ;
1147
1158
}
1148
1159
} ) ) ;
1149
1160
@@ -1174,14 +1185,18 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1174
1185
return false ;
1175
1186
}
1176
1187
1177
- private updateDropFeedback ( element : HTMLElement , isDND : boolean , tabIndex ?: number ) : void {
1188
+ private updateDropFeedback ( element : HTMLElement , isDND : boolean , e : DragEvent , tabIndex ?: number ) : void {
1178
1189
const isTab = ( typeof tabIndex === 'number' ) ;
1179
- const editor = typeof tabIndex === 'number' ? this . tabsModel . getEditorByIndex ( tabIndex ) : undefined ;
1180
- const isActiveTab = isTab && ! ! editor && this . tabsModel . isActive ( editor ) ;
1181
1190
1182
- // Background
1183
- const noDNDBackgroundColor = isTab ? this . getColor ( isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND ) : '' ;
1184
- element . style . backgroundColor = ( isDND ? this . getColor ( EDITOR_DRAG_AND_DROP_BACKGROUND ) : noDNDBackgroundColor ) || '' ;
1191
+ if ( isDND ) {
1192
+ if ( isTab ) {
1193
+ this . dndDropTarget = this . computeDropTarget ( e , tabIndex , element ) ;
1194
+ } else {
1195
+ this . dndDropTarget = { leftElement : element . lastElementChild as HTMLElement , rightElement : undefined } ;
1196
+ }
1197
+ } else {
1198
+ this . dndDropTarget = undefined ;
1199
+ }
1185
1200
1186
1201
// Outline
1187
1202
const activeContrastBorderColor = this . getColor ( activeContrastBorder ) ;
@@ -1198,6 +1213,34 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1198
1213
}
1199
1214
}
1200
1215
1216
+ private isHeadOfTab ( e : DragEvent , tab : HTMLElement ) : boolean {
1217
+ const rect = tab . getBoundingClientRect ( ) ;
1218
+ const offsetXRelativeToParent = e . clientX - rect . left ;
1219
+ return offsetXRelativeToParent <= rect . width / 2 ;
1220
+ }
1221
+
1222
+ private computeDropTarget ( e : DragEvent , tabIndex : number , targetTab : HTMLElement ) : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined {
1223
+ const isHeadOfTab = this . isHeadOfTab ( e , targetTab ) ;
1224
+ const isLastTab = tabIndex === this . tabsModel . count - 1 ;
1225
+ const isFirstTab = tabIndex === 0 ;
1226
+
1227
+ // Before first tab
1228
+ if ( isHeadOfTab && isFirstTab ) {
1229
+ return { leftElement : undefined , rightElement : targetTab } ;
1230
+ }
1231
+
1232
+ // After last tab
1233
+ if ( ! isHeadOfTab && isLastTab ) {
1234
+ return { leftElement : targetTab , rightElement : undefined } ;
1235
+ }
1236
+
1237
+ // Between two tabs
1238
+ const tabBefore = isHeadOfTab ? targetTab . previousElementSibling : targetTab ;
1239
+ const tabAfter = isHeadOfTab ? targetTab : targetTab . nextElementSibling ;
1240
+
1241
+ return { leftElement : tabBefore as HTMLElement , rightElement : tabAfter as HTMLElement } ;
1242
+ }
1243
+
1201
1244
private computeTabLabels ( ) : void {
1202
1245
const { labelFormat } = this . groupsView . partOptions ;
1203
1246
const { verbosity, shortenDuplicates } = this . getLabelConfigFlags ( labelFormat ) ;
@@ -2044,7 +2087,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
2044
2087
private async onDrop ( e : DragEvent , targetTabIndex : number , tabsContainer : HTMLElement ) : Promise < void > {
2045
2088
EventHelper . stop ( e , true ) ;
2046
2089
2047
- this . updateDropFeedback ( tabsContainer , false ) ;
2090
+ this . updateDropFeedback ( tabsContainer , false , e , targetTabIndex ) ;
2048
2091
tabsContainer . classList . remove ( 'scroll' ) ;
2049
2092
2050
2093
const targetEditorIndex = this . tabsModel instanceof UnstickyEditorGroupModel ? targetTabIndex + this . groupView . stickyCount : targetTabIndex ;
@@ -2367,4 +2410,16 @@ registerThemingParticipant((theme, collector) => {
2367
2410
collector . addRule ( makeTabBackgroundRule ( adjustedColor , adjustedColorDrag , false , false ) ) ;
2368
2411
}
2369
2412
}
2413
+
2414
+ const tabDndIndicatorColor = theme . getColor ( TAB_Drag_And_Drop_Between_Indicator ) ;
2415
+ if ( tabDndIndicatorColor ) {
2416
+ // DnD Feedback
2417
+
2418
+ collector . addRule ( `
2419
+ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-left::after,
2420
+ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.drop-target-right::before {
2421
+ background-color: ${ tabDndIndicatorColor } ;
2422
+ }
2423
+ ` ) ;
2424
+ }
2370
2425
} ) ;
0 commit comments