未验证 提交 75a39c83 编写于 作者: J João Moreno 提交者: GitHub

Merge pull request #66291 from Microsoft/joao/tree-dnd

Implement List & new Tree drag and drop
......@@ -83,4 +83,17 @@ export function applyDragImage(event: DragEvent, label: string, clazz: string):
// Removes the element when the DND operation is done
setTimeout(() => document.body.removeChild(dragImage), 0);
}
}
\ No newline at end of file
}
export interface IDragAndDropData {
update(dataTransfer: DataTransfer): void;
getData(): any;
}
export interface IStaticDND {
CurrentDragAndDropData: IDragAndDropData | undefined;
}
export const StaticDND: IStaticDND = {
CurrentDragAndDropData: undefined
};
\ No newline at end of file
......@@ -11,7 +11,7 @@ import { TimeoutTimer } from 'vs/base/common/async';
import { CharCode } from 'vs/base/common/charCode';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { coalesce } from 'vs/base/common/arrays';
......@@ -1157,3 +1157,18 @@ export function windowOpenNoOpener(url: string): void {
}
}
}
export function animate(fn: () => void): IDisposable {
const step = () => {
fn();
stepDisposable = scheduleAtNextAnimationFrame(step);
};
let stepDisposable = scheduleAtNextAnimationFrame(step);
return toDisposable(() => stepDisposable.dispose());
}
export function timeout(fn: () => void, millis: number): IDisposable {
const timer = setTimeout(fn, millis);
return toDisposable(() => clearTimeout(timer));
}
\ No newline at end of file
......@@ -52,4 +52,13 @@
/* Focus */
.monaco-list.element-focused, .monaco-list.selection-single, .monaco-list.selection-multiple {
outline: 0 !important;
}
/* Dnd */
.monaco-list-drag-image {
display: inline-block;
padding: 1px 7px;
border-radius: 10px;
font-size: 12px;
position: absolute;
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ import { GestureEvent } from 'vs/base/browser/touch';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDragAndDropData } from 'vs/base/browser/dnd';
export interface IListVirtualDelegate<T> {
getHeight(element: T): number;
......@@ -46,6 +47,12 @@ export interface IListGestureEvent<T> {
index: number | undefined;
}
export interface IListDragEvent<T> {
browserEvent: DragEvent;
element: T | undefined;
index: number | undefined;
}
export interface IListContextMenuEvent<T> {
browserEvent: UIEvent;
element: T | undefined;
......@@ -62,6 +69,30 @@ export interface IKeyboardNavigationLabelProvider<T> {
mightProducePrintableCharacter?(event: IKeyboardEvent): boolean;
}
export const enum ListDragOverEffect {
Copy,
Move
}
export interface IListDragOverReaction {
accept: boolean;
effect?: ListDragOverEffect;
feedback?: number[]; // use -1 for entire list
}
export const ListDragOverReactions = {
reject(): IListDragOverReaction { return { accept: false }; },
accept(): IListDragOverReaction { return { accept: true }; },
};
export interface IListDragAndDrop<T> {
getDragURI(element: T): string | null;
getDragLabel?(elements: T[]): string | undefined;
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void;
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
}
/**
* Use this renderer when you want to re-render elements on account of
* an event firing.
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { getOrDefault } from 'vs/base/common/objects';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
import * as DOM from 'vs/base/browser/dom';
import { Event, Emitter } from 'vs/base/common/event';
......@@ -12,14 +12,15 @@ import { domEvent } from 'vs/base/browser/event';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable';
import { RangeMap, shift } from './rangeMap';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IListDragAndDrop, ListDragOverEffect } from './list';
import { RowCache, IRow } from './rowCache';
import { isWindows } from 'vs/base/common/platform';
import * as browser from 'vs/base/browser/browser';
import { ISpliceable } from 'vs/base/common/sequence';
import { memoize } from 'vs/base/common/decorators';
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { Range, IRange } from 'vs/base/common/range';
import { equals, distinct } from 'vs/base/common/arrays';
import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd';
function canUseTranslate3d(): boolean {
if (browser.isFirefox) {
......@@ -41,9 +42,17 @@ interface IItem<T> {
size: number;
hasDynamicHeight: boolean;
renderWidth: number | undefined;
uri: string | undefined;
dropTarget: boolean;
dragStartDisposable: IDisposable;
}
export interface IListViewOptions {
export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> {
getDragElements(element: T): T[];
}
export interface IListViewOptions<T> {
readonly dnd?: IListViewDragAndDrop<T>;
readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean;
......@@ -55,9 +64,94 @@ const DefaultOptions = {
useShadows: true,
verticalScrollMode: ScrollbarVisibility.Auto,
setRowLineHeight: true,
supportDynamicHeights: false
supportDynamicHeights: false,
dnd: {
getDragElements(e) { return [e]; },
getDragURI() { return null; },
onDragStart(): void { },
onDragOver() { return false; },
drop() { }
}
};
export class ElementsDragAndDropData<T> implements IDragAndDropData {
private elements: T[];
constructor(elements: T[]) {
this.elements = elements;
}
public update(dataTransfer: DataTransfer): void {
// no-op
}
public getData(): any {
return this.elements;
}
}
export class ExternalElementsDragAndDropData<T> implements IDragAndDropData {
private elements: T[];
constructor(elements: T[]) {
this.elements = elements;
}
public update(dataTransfer: DataTransfer): void {
// no-op
}
public getData(): any {
return this.elements;
}
}
export class DesktopDragAndDropData implements IDragAndDropData {
private types: any[];
private files: any[];
constructor() {
this.types = [];
this.files = [];
}
public update(dataTransfer: DataTransfer): void {
if (dataTransfer.types) {
this.types = [...dataTransfer.types];
}
if (dataTransfer.files) {
this.files = [];
for (let i = 0; i < dataTransfer.files.length; i++) {
const file = dataTransfer.files.item(i);
if (file && (file.size || file.type)) {
this.files.push(file);
}
}
}
}
public getData(): any {
return {
types: this.types,
files: this.files
};
}
}
function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): boolean {
if (Array.isArray(f1) && Array.isArray(f2)) {
return equals(f1, f2!);
}
return f1 === f2;
}
export class ListView<T> implements ISpliceable<T>, IDisposable {
readonly domNode: HTMLElement;
......@@ -76,22 +170,37 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private _scrollHeight: number;
private scrollableElementUpdateDisposable: IDisposable | null = null;
private splicing = false;
private dragAndDropScrollInterval: number;
private dragAndDropScrollTimeout: number;
private dragAndDropMouseY: number;
private dragOverAnimationDisposable: IDisposable | undefined;
private dragOverAnimationStopDisposable: IDisposable = Disposable.None;
private dragOverMouseY: number;
private setRowLineHeight: boolean;
private supportDynamicHeights: boolean;
private dnd: IListViewDragAndDrop<T>;
private currentDragData: IDragAndDropData | undefined;
private currentDragFeedback: number[] | undefined;
private currentDragFeedbackDisposable: IDisposable = Disposable.None;
private onDragLeaveTimeout: IDisposable = Disposable.None;
private disposables: IDisposable[];
private _onDidChangeContentHeight = new Emitter<number>();
readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event);
get contentHeight(): number { return this.rangeMap.size; }
// private _onDragStart = new Emitter<{ element: T, uri: string, event: DragEvent }>();
// readonly onDragStart = this._onDragStart.event;
// readonly onDragOver: Event<IListDragEvent<T>>;
// readonly onDragLeave: Event<void>;
// readonly onDrop: Event<IListDragEvent<T>>;
// readonly onDragEnd: Event<void>;
constructor(
container: HTMLElement,
private virtualDelegate: IListVirtualDelegate<T>,
renderers: IListRenderer<any /* TODO@joao */, any>[],
options: IListViewOptions = DefaultOptions
options: IListViewOptions<T> = DefaultOptions
) {
this.items = [];
this.itemId = 0;
......@@ -134,11 +243,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
domEvent(this.scrollableElement.getDomNode(), 'scroll')
(e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables);
const onDragOver = Event.map(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e));
onDragOver(this.onDragOver, this, this.disposables);
Event.map(domEvent(this.domNode, 'dragover'), e => this.toDragEvent(e))(this.onDragOver, this, this.disposables);
Event.map(domEvent(this.domNode, 'drop'), e => this.toDragEvent(e))(this.onDrop, this, this.disposables);
domEvent(this.domNode, 'dragleave')(this.onDragLeave, this, this.disposables);
domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables);
this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
this.layout();
}
......@@ -178,7 +290,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
size: this.virtualDelegate.getHeight(element),
hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
renderWidth: undefined,
row: null
row: null,
uri: undefined,
dropTarget: false,
dragStartDisposable: Disposable.None
}));
let deleted: IItem<T>[];
......@@ -354,6 +469,15 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const renderer = this.renderers.get(item.templateId);
renderer.renderElement(item.element, index, item.row.templateData);
const uri = this.dnd.getDragURI(item.element);
item.dragStartDisposable.dispose();
if (uri) {
item.row.domNode!.draggable = true;
const onDragStart = domEvent(item.row.domNode!, 'dragstart');
item.dragStartDisposable = onDragStart(event => this.onDragStart(item.element, uri, event));
}
}
private updateItemInDOM(item: IItem<T>, index: number): void {
......@@ -368,10 +492,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`);
item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`);
DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget);
}
private removeItemFromDOM(index: number): void {
const item = this.items[index];
item.dragStartDisposable.dispose();
const renderer = this.renderers.get(item.templateId);
if (renderer.disposeElement) {
......@@ -444,6 +571,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
return { browserEvent, index, element };
}
private toDragEvent(browserEvent: DragEvent): IListDragEvent<T> {
const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
const item = typeof index === 'undefined' ? undefined : this.items[index];
const element = item && item.element;
return { browserEvent, index, element };
}
private onScroll(e: ScrollEvent): void {
try {
this.render(e.scrollTop, e.height);
......@@ -464,55 +598,204 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollTop -= event.translationY;
}
private onDragOver(event: DragMouseEvent): void {
this.setupDragAndDropScrollInterval();
this.dragAndDropMouseY = event.posy;
// DND
private onDragStart(element: T, uri: string, event: DragEvent): void {
if (!event.dataTransfer) {
return;
}
const elements = this.dnd.getDragElements(element);
event.dataTransfer.effectAllowed = 'copyMove';
event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri]));
if (event.dataTransfer.setDragImage) {
let label: string | undefined;
if (this.dnd.getDragLabel) {
label = this.dnd.getDragLabel(elements);
}
if (typeof label === 'undefined') {
label = String(elements.length);
}
const dragImage = DOM.$('.monaco-list-drag-image');
dragImage.textContent = label;
document.body.appendChild(dragImage);
event.dataTransfer.setDragImage(dragImage, -10, -10);
setTimeout(() => document.body.removeChild(dragImage), 0);
}
this.currentDragData = new ElementsDragAndDropData(elements);
StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(elements);
this.dnd.onDragStart(this.currentDragData, event);
}
private setupDragAndDropScrollInterval(): void {
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
private onDragOver(event: IListDragEvent<T>): boolean {
this.onDragLeaveTimeout.dispose();
this.setupDragAndDropScrollTopAnimation(event.browserEvent);
if (!event.browserEvent.dataTransfer) {
return false;
}
if (!this.dragAndDropScrollInterval) {
this.dragAndDropScrollInterval = window.setInterval(() => {
if (this.dragAndDropMouseY === undefined) {
return;
// Drag over from outside
if (!this.currentDragData) {
if (StaticDND.CurrentDragAndDropData) {
// Drag over from another list
this.currentDragData = StaticDND.CurrentDragAndDropData;
} else {
// Drag over from the desktop
if (!event.browserEvent.dataTransfer.types) {
return false;
}
let diff = this.dragAndDropMouseY - viewTop;
let scrollDiff = 0;
let upperLimit = this.renderHeight - 35;
this.currentDragData = new DesktopDragAndDropData();
}
}
const result = this.dnd.onDragOver(this.currentDragData, event.element, event.index, event.browserEvent);
const canDrop = typeof result === 'boolean' ? result : result.accept;
if (!canDrop) {
return false;
}
event.browserEvent.dataTransfer.dropEffect = (typeof result !== 'boolean' && result.effect === ListDragOverEffect.Copy) ? 'copy' : 'move';
let feedback: number[];
if (diff < 35) {
scrollDiff = Math.max(-14, 0.2 * (diff - 35));
} else if (diff > upperLimit) {
scrollDiff = Math.min(14, 0.2 * (diff - upperLimit));
if (typeof result !== 'boolean' && result.feedback) {
feedback = result.feedback;
} else {
if (typeof event.index === 'undefined') {
feedback = [-1];
} else {
feedback = [event.index];
}
}
// sanitize feedback list
feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
feedback = feedback[0] === -1 ? [-1] : feedback;
if (feedback.length === 0) {
throw new Error('Invalid empty feedback list');
}
if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
return true;
}
this.currentDragFeedback = feedback;
this.currentDragFeedbackDisposable.dispose();
if (feedback[0] === -1) { // entire list feedback
DOM.addClass(this.domNode, 'drop-target');
this.currentDragFeedbackDisposable = toDisposable(() => DOM.removeClass(this.domNode, 'drop-target'));
} else {
for (const index of feedback) {
const item = this.items[index]!;
item.dropTarget = true;
if (item.row && item.row.domNode) {
DOM.addClass(item.row.domNode, 'drop-target');
}
}
this.scrollTop += scrollDiff;
}, 10);
this.currentDragFeedbackDisposable = toDisposable(() => {
for (const index of feedback) {
const item = this.items[index]!;
item.dropTarget = false;
this.cancelDragAndDropScrollTimeout();
if (item.row && item.row.domNode) {
DOM.removeClass(item.row.domNode, 'drop-target');
}
}
});
}
return true;
}
this.dragAndDropScrollTimeout = window.setTimeout(() => {
this.cancelDragAndDropScrollInterval();
this.dragAndDropScrollTimeout = -1;
}, 1000);
private onDragLeave(): void {
this.onDragLeaveTimeout.dispose();
this.onDragLeaveTimeout = DOM.timeout(() => this.clearDragOverFeedback(), 100);
}
private onDrop(event: IListDragEvent<T>): void {
const dragData = this.currentDragData;
this.teardownDragAndDropScrollTopAnimation();
this.clearDragOverFeedback();
this.currentDragData = undefined;
StaticDND.CurrentDragAndDropData = undefined;
if (!dragData || !event.browserEvent.dataTransfer) {
return;
}
event.browserEvent.preventDefault();
dragData.update(event.browserEvent.dataTransfer);
this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
}
private cancelDragAndDropScrollInterval(): void {
if (this.dragAndDropScrollInterval) {
window.clearInterval(this.dragAndDropScrollInterval);
this.dragAndDropScrollInterval = -1;
private onDragEnd(): void {
this.teardownDragAndDropScrollTopAnimation();
this.clearDragOverFeedback();
this.currentDragData = undefined;
StaticDND.CurrentDragAndDropData = undefined;
}
private clearDragOverFeedback(): void {
this.currentDragFeedback = undefined;
this.currentDragFeedbackDisposable.dispose();
this.currentDragFeedbackDisposable = Disposable.None;
}
// DND scroll top animation
private setupDragAndDropScrollTopAnimation(event: DragEvent): void {
if (!this.dragOverAnimationDisposable) {
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
this.dragOverAnimationDisposable = DOM.animate(this.animateDragAndDropScrollTop.bind(this, viewTop));
}
this.dragOverAnimationStopDisposable.dispose();
this.dragOverAnimationStopDisposable = DOM.timeout(() => {
if (this.dragOverAnimationDisposable) {
this.dragOverAnimationDisposable.dispose();
this.dragOverAnimationDisposable = undefined;
}
}, 1000);
this.dragOverMouseY = event.pageY;
}
private animateDragAndDropScrollTop(viewTop: number): void {
if (this.dragOverMouseY === undefined) {
return;
}
this.cancelDragAndDropScrollTimeout();
const diff = this.dragOverMouseY - viewTop;
const upperLimit = this.renderHeight - 35;
if (diff < 35) {
this.scrollTop += Math.max(-14, Math.floor(0.3 * (diff - 35)));
} else if (diff > upperLimit) {
this.scrollTop += Math.min(14, Math.floor(0.3 * (diff - upperLimit)));
}
}
private cancelDragAndDropScrollTimeout(): void {
if (this.dragAndDropScrollTimeout) {
window.clearTimeout(this.dragAndDropScrollTimeout);
this.dragAndDropScrollTimeout = -1;
private teardownDragAndDropScrollTopAnimation(): void {
this.dragOverAnimationStopDisposable.dispose();
if (this.dragOverAnimationDisposable) {
this.dragOverAnimationDisposable.dispose();
this.dragOverAnimationDisposable = undefined;
}
}
......
......@@ -16,8 +16,8 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider } from './list';
import { ListView, IListViewOptions } from './listView';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction } from './list';
import { ListView, IListViewOptions, IListViewDragAndDrop } from './listView';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
......@@ -25,6 +25,7 @@ import { ISpliceable } from 'vs/base/common/sequence';
import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
import { clamp } from 'vs/base/common/numbers';
import { matchesPrefix } from 'vs/base/common/filters';
import { IDragAndDropData } from 'vs/base/browser/dnd';
interface ITraitChangeEvent {
indexes: number[];
......@@ -212,7 +213,7 @@ class TraitSpliceable<T> implements ISpliceable<T> {
splice(start: number, deleteCount: number, elements: T[]): void {
if (!this.identityProvider) {
return this.trait.splice(start, deleteCount, elements.map(e => false));
return this.trait.splice(start, deleteCount, elements.map(() => false));
}
const pastElementsWithTrait = this.trait.get().map(i => this.identityProvider!.getId(this.view.element(i)).toString());
......@@ -477,7 +478,7 @@ class MouseController<T> implements IDisposable {
constructor(
private list: List<T>,
private view: ListView<T>,
private options: IListOptions<T> = {}
options: IListOptions<T> = {}
) {
this.multipleSelectionSupport = !(options.multipleSelectionSupport === false);
......@@ -488,6 +489,7 @@ class MouseController<T> implements IDisposable {
this.openController = options.openController || DefaultOpenController;
view.onMouseDown(this.onMouseDown, this, this.disposables);
view.onContextMenu(this.onContextMenu, this, this.disposables);
view.onMouseClick(this.onPointer, this, this.disposables);
view.onMouseDblClick(this.onDoubleClick, this, this.disposables);
view.onTouchStart(this.onMouseDown, this, this.disposables);
......@@ -516,13 +518,17 @@ class MouseController<T> implements IDisposable {
}
private onMouseDown(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
if (this.options.focusOnMouseDown === false) {
e.browserEvent.preventDefault();
e.browserEvent.stopPropagation();
} else if (document.activeElement !== e.browserEvent.target) {
if (document.activeElement !== e.browserEvent.target) {
this.view.domNode.focus();
}
}
private onContextMenu(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
const focus = typeof e.index === 'undefined' ? [] : [e.index];
this.list.setFocus(focus, e.browserEvent);
}
private onPointer(e: IListMouseEvent<T>): void {
let reference = this.list.getFocus()[0];
const selection = this.list.getSelection();
reference = reference === undefined ? selection[0] : reference;
......@@ -539,15 +545,13 @@ class MouseController<T> implements IDisposable {
return this.changeSelection(e, reference);
}
if (selection.every(s => s !== focus)) {
this.list.setFocus([focus], e.browserEvent);
}
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
return this.changeSelection(e, reference);
}
if (this.options.selectOnMouseDown && !isMouseRightClick(e.browserEvent)) {
this.list.setFocus([focus], e.browserEvent);
if (!isMouseRightClick(e.browserEvent)) {
this.list.setSelection([focus], e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
......@@ -556,21 +560,6 @@ class MouseController<T> implements IDisposable {
}
}
private onPointer(e: IListMouseEvent<T>): void {
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
return;
}
if (!this.options.selectOnMouseDown) {
const focus = this.list.getFocus();
this.list.setSelection(focus, e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open(focus, e.browserEvent);
}
}
}
private onDoubleClick(e: IListMouseEvent<T>): void {
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
return;
......@@ -670,11 +659,17 @@ export class DefaultStyleController implements IStyleController {
}
if (styles.listFocusAndSelectionBackground) {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }`);
content.push(`
.monaco-list-drag-image,
.monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }
`);
}
if (styles.listFocusAndSelectionForeground) {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }`);
content.push(`
.monaco-list-drag-image,
.monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }
`);
}
if (styles.listInactiveFocusBackground) {
......@@ -704,7 +699,10 @@ export class DefaultStyleController implements IStyleController {
}
if (styles.listFocusOutline) {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
content.push(`
.monaco-list-drag-image,
.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }
`);
}
if (styles.listInactiveFocusOutline) {
......@@ -715,6 +713,13 @@ export class DefaultStyleController implements IStyleController {
content.push(`.monaco-list${suffix} .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`);
}
if (styles.listDropBackground) {
content.push(`
.monaco-list${suffix}.drop-target,
.monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
`);
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.innerHTML) {
this.styleElement.innerHTML = newStyles;
......@@ -722,20 +727,24 @@ export class DefaultStyleController implements IStyleController {
}
}
export interface IListOptions<T> extends IListViewOptions, IListStyles {
identityProvider?: IIdentityProvider<T>;
keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
ariaLabel?: string;
mouseSupport?: boolean;
selectOnMouseDown?: boolean;
focusOnMouseDown?: boolean;
keyboardSupport?: boolean;
verticalScrollMode?: ScrollbarVisibility;
multipleSelectionSupport?: boolean;
multipleSelectionController?: IMultipleSelectionController<T>;
openController?: IOpenController;
styleController?: IStyleController;
accessibilityProvider?: IAccessibilityProvider<T>;
export interface IListOptions<T> extends IListStyles {
readonly identityProvider?: IIdentityProvider<T>;
readonly dnd?: IListDragAndDrop<T>;
readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
readonly ariaLabel?: string;
readonly keyboardSupport?: boolean;
readonly multipleSelectionSupport?: boolean;
readonly multipleSelectionController?: IMultipleSelectionController<T>;
readonly openController?: IOpenController;
readonly styleController?: IStyleController;
readonly accessibilityProvider?: IAccessibilityProvider<T>;
// list view options
readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean;
readonly supportDynamicHeights?: boolean;
readonly mouseSupport?: boolean;
}
export interface IListStyles {
......@@ -768,10 +777,16 @@ const defaultStyles: IListStyles = {
listDropBackground: Color.fromHex('#383B3D')
};
const DefaultOptions: IListOptions<any> = {
const DefaultOptions = {
keyboardSupport: true,
mouseSupport: true,
multipleSelectionSupport: true
multipleSelectionSupport: true,
dnd: {
getDragURI() { return null; },
onDragStart(): void { },
onDragOver() { return false; },
drop() { }
}
};
// TODO@Joao: move these utils into a SortedArray class
......@@ -926,6 +941,37 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
}
}
class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
constructor(private list: List<T>, private dnd: IListDragAndDrop<T>) { }
getDragElements(element: T): T[] {
const selection = this.list.getSelectedElements();
const elements = selection.indexOf(element) > -1 ? selection : [element];
return elements;
}
getDragURI(element: T): string | null {
return this.dnd.getDragURI(element);
}
getDragLabel?(elements: T[]): string | undefined {
return this.dnd.getDragLabel && this.dnd.getDragLabel(elements);
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
this.dnd.onDragStart(data, originalEvent);
}
onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): boolean | IListDragOverReaction {
return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
}
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
}
}
export class List<T> implements ISpliceable<T>, IDisposable {
private static InstanceCount = 0;
......@@ -936,10 +982,11 @@ export class List<T> implements ISpliceable<T>, IDisposable {
private eventBufferer = new EventBufferer();
private view: ListView<T>;
private spliceable: ISpliceable<T>;
protected disposables: IDisposable[];
private styleElement: HTMLStyleElement;
private styleController: IStyleController;
protected disposables: IDisposable[];
@memoize get onFocusChange(): Event<IListEvent<T>> {
return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
}
......@@ -1034,7 +1081,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
this.view = new ListView(container, virtualDelegate, renderers, options);
const viewOptions: IListViewOptions<T> = {
...options,
dnd: options.dnd && new ListViewDragAndDrop(this, options.dnd)
};
this.view = new ListView(container, virtualDelegate, renderers, viewOptions);
this.view.domNode.setAttribute('role', 'tree');
DOM.addClass(this.view.domNode, this.idPrefix);
this.view.domNode.tabIndex = 0;
......
......@@ -731,7 +731,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this.selectList = new List(this.selectDropDownListContainer, this, [this.listRenderer], {
ariaLabel: this.selectBoxOptions.ariaLabel,
useShadows: false,
selectOnMouseDown: false,
verticalScrollMode: ScrollbarVisibility.Visible,
keyboardSupport: false,
mouseSupport: false
......
......@@ -4,17 +4,95 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IListOptions, List, IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { append, $, toggleClass } from 'vs/base/browser/dom';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
import { append, $, toggleClass, timeout } from 'vs/base/browser/dom';
import { Event, Relay } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { range } from 'vs/base/common/arrays';
function asListOptions<T, TFilterData>(options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<ITreeNode<T, TFilterData>> {
private autoExpandNode: ITreeNode<T, TFilterData> | undefined;
private autoExpandDisposable: IDisposable = Disposable.None;
constructor(private modelProvider: () => ITreeModel<T, TFilterData, TRef>, private dnd: ITreeDragAndDrop<T>) { }
getDragURI(node: ITreeNode<T, TFilterData>): string | null {
return this.dnd.getDragURI(node.element);
}
getDragLabel(nodes: ITreeNode<T, TFilterData>[]): string | undefined {
return this.dnd.getDragLabel && this.dnd.getDragLabel(nodes.map(node => node.element));
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
this.dnd.onDragStart(data, originalEvent);
}
onDragOver(data: IDragAndDropData, targetNode: ITreeNode<T, TFilterData> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
const result = this.dnd.onDragOver(data, targetNode && targetNode.element, targetIndex, originalEvent);
const didChangeAutoExpandNode = this.autoExpandNode !== targetNode;
if (didChangeAutoExpandNode) {
this.autoExpandDisposable.dispose();
this.autoExpandNode = targetNode;
}
if (typeof targetNode === 'undefined') {
return result;
}
if (didChangeAutoExpandNode && typeof result !== 'boolean' && result.autoExpand) {
this.autoExpandDisposable = timeout(() => {
const model = this.modelProvider();
const ref = model.getNodeLocation(targetNode);
if (model.isCollapsed(ref)) {
model.setCollapsed(ref, false);
}
this.autoExpandNode = undefined;
}, 500);
}
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
if (!raw) {
const accept = typeof result === 'boolean' ? result : result.accept;
const effect = typeof result === 'boolean' ? undefined : result.effect;
return { accept, effect, feedback: [targetIndex!] };
}
return result;
}
if (result.bubble === TreeDragOverBubble.Up) {
const parentNode = targetNode.parent;
const model = this.modelProvider();
const parentIndex = parentNode && model.getListIndex(model.getNodeLocation(parentNode));
return this.onDragOver(data, parentNode, parentIndex, originalEvent, false);
}
const model = this.modelProvider();
const ref = model.getNodeLocation(targetNode);
const start = model.getListIndex(ref);
const length = model.getListRenderCount(ref);
return { ...result, feedback: range(start, start + length) };
}
drop(data: IDragAndDropData, targetNode: ITreeNode<T, TFilterData> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
this.dnd.drop(data, targetNode && targetNode.element, targetIndex, originalEvent);
}
}
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
return options && {
...options,
identityProvider: options.identityProvider && {
......@@ -22,6 +100,7 @@ function asListOptions<T, TFilterData>(options?: IAbstractTreeOptions<T, TFilter
return options.identityProvider!.getId(el.element);
}
},
dnd: options.dnd && new TreeNodeListDragAndDrop(modelProvider, options.dnd),
multipleSelectionController: options.multipleSelectionController && {
isSelectionSingleChangeEvent(e) {
return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
......@@ -181,6 +260,7 @@ function asTreeContextMenuEvent<T>(event: IListContextMenuEvent<ITreeNode<T, any
export interface IAbstractTreeOptions<T, TFilterData = void> extends IListOptions<T> {
collapseByDefault?: boolean; // defaults to false
filter?: ITreeFilter<T, TFilterData>;
readonly dnd?: ITreeDragAndDrop<T>;
}
export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable {
......@@ -221,7 +301,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
const treeRenderers = renderers.map(r => new TreeRenderer<T, TFilterData, any>(r, onDidChangeCollapseStateRelay.event));
this.disposables.push(...treeRenderers);
this.view = new List(container, treeDelegate, treeRenderers, asListOptions(options));
this.view = new List(container, treeDelegate, treeRenderers, asListOptions(() => this.model, options));
this.model = this.createModel(this.view, options);
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
......
......@@ -130,6 +130,23 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
return options.identityProvider!.getId(el.element as T);
}
},
dnd: options.dnd && {
getDragURI(node) {
return options.dnd!.getDragURI(node.element as T);
},
getDragLabel: options.dnd!.getDragLabel && ((nodes) => {
return options.dnd!.getDragLabel!(nodes.map(node => node.element as T));
}),
onDragStart(data, originalEvent) {
return options.dnd!.onDragStart(data, originalEvent);
},
onDragOver(data, targetNode, targetIndex, originalEvent) {
return options.dnd!.onDragOver(data, targetNode && targetNode.element as T, targetIndex, originalEvent);
},
drop(data, targetNode, targetIndex, originalEvent) {
return options.dnd!.drop(data, targetNode && targetNode.element as T, targetIndex, originalEvent);
}
},
multipleSelectionController: options.multipleSelectionController && {
isSelectionSingleChangeEvent(e) {
return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
......
......@@ -127,6 +127,10 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return this.getTreeNodeWithListIndex(location).listIndex;
}
getListRenderCount(location: number[]): number {
return this.getTreeNode(location).renderNodeCount;
}
isCollapsible(location: number[]): boolean {
return this.getTreeNode(location).collapsible;
}
......
......@@ -123,6 +123,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
return this.model.getListIndex(location);
}
getListRenderCount(element: T): number {
const location = this.getElementLocation(element);
return this.model.getListRenderCount(location);
}
isCollapsible(element: T): boolean {
const location = this.getElementLocation(element);
return this.model.isCollapsible(location);
......
......@@ -5,7 +5,8 @@
import { Event } from 'vs/base/common/event';
import { Iterator } from 'vs/base/common/iterator';
import { IListRenderer, AbstractListRenderer } from 'vs/base/browser/ui/list/list';
import { IListRenderer, AbstractListRenderer, IListDragOverReaction, IListDragAndDrop, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
import { IDragAndDropData } from 'vs/base/browser/dnd';
export const enum TreeVisibility {
......@@ -100,6 +101,7 @@ export interface ITreeModel<T, TFilterData, TRef> {
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>>;
getListIndex(location: TRef): number;
getListRenderCount(location: TRef): number;
getNode(location?: TRef): ITreeNode<T, any>;
getNodeLocation(node: ITreeNode<T, any>): TRef;
getParentNodeLocation(location: TRef): TRef;
......@@ -154,6 +156,27 @@ export interface IAsyncDataSource<TInput, T> {
getChildren(element: TInput | T): T[] | Promise<T[]>;
}
export const enum TreeDragOverBubble {
Down,
Up
}
export interface ITreeDragOverReaction extends IListDragOverReaction {
bubble?: TreeDragOverBubble;
autoExpand?: boolean;
}
export const TreeDragOverReactions = {
acceptBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up }; },
acceptBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand }; },
acceptCopyBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up, effect: ListDragOverEffect.Copy }; },
acceptCopyBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, effect: ListDragOverEffect.Copy, autoExpand }; }
};
export interface ITreeDragAndDrop<T> extends IListDragAndDrop<T> {
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction;
}
/**
* Use this renderer when you want to re-render elements on account of
* an event firing.
......
......@@ -12,6 +12,7 @@ import { Event } from 'vs/base/common/event';
import { IAction, IActionItem } from 'vs/base/common/actions';
import { Color } from 'vs/base/common/color';
import { IItemCollapseEvent, IItemExpandEvent } from 'vs/base/parts/tree/browser/treeModel';
import { IDragAndDropData } from 'vs/base/browser/dnd';
export interface ITree {
......@@ -593,11 +594,6 @@ export const DRAG_OVER_ACCEPT_BUBBLE_DOWN = (autoExpand = false) => ({ accept: t
export const DRAG_OVER_ACCEPT_BUBBLE_UP_COPY: IDragOverReaction = { accept: true, bubble: DragOverBubble.BUBBLE_UP, effect: DragOverEffect.COPY };
export const DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY = (autoExpand = false) => ({ accept: true, bubble: DragOverBubble.BUBBLE_DOWN, effect: DragOverEffect.COPY, autoExpand });
export interface IDragAndDropData {
update(event: Mouse.DragMouseEvent): void;
getData(): any;
}
export interface IDragAndDrop {
/**
......
......@@ -12,6 +12,7 @@ import * as dom from 'vs/base/browser/dom';
import * as mouse from 'vs/base/browser/mouseEvent';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import * as _ from 'vs/base/parts/tree/browser/tree';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { KeyCode, KeyMod, Keybinding, SimpleKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes';
export interface IKeyBindingCallback {
......@@ -434,15 +435,15 @@ export class DefaultDragAndDrop implements _.IDragAndDrop {
return null;
}
public onDragStart(tree: _.ITree, data: _.IDragAndDropData, originalEvent: mouse.DragMouseEvent): void {
public onDragStart(tree: _.ITree, data: IDragAndDropData, originalEvent: mouse.DragMouseEvent): void {
return;
}
public onDragOver(tree: _.ITree, data: _.IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): _.IDragOverReaction | null {
public onDragOver(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): _.IDragOverReaction | null {
return null;
}
public drop(tree: _.ITree, data: _.IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): void {
public drop(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): void {
return;
}
}
......
......@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as _ from 'vs/base/parts/tree/browser/tree';
import * as Mouse from 'vs/base/browser/mouseEvent';
import { IDragAndDropData } from 'vs/base/browser/dnd';
export class ElementsDragAndDropData implements _.IDragAndDropData {
export class ElementsDragAndDropData implements IDragAndDropData {
private elements: any[];
......@@ -14,7 +14,7 @@ export class ElementsDragAndDropData implements _.IDragAndDropData {
this.elements = elements;
}
public update(event: Mouse.DragMouseEvent): void {
public update(dataTransfer: DataTransfer): void {
// no-op
}
......@@ -23,7 +23,7 @@ export class ElementsDragAndDropData implements _.IDragAndDropData {
}
}
export class ExternalElementsDragAndDropData implements _.IDragAndDropData {
export class ExternalElementsDragAndDropData implements IDragAndDropData {
private elements: any[];
......@@ -31,7 +31,7 @@ export class ExternalElementsDragAndDropData implements _.IDragAndDropData {
this.elements = elements;
}
public update(event: Mouse.DragMouseEvent): void {
public update(dataTransfer: DataTransfer): void {
// no-op
}
......@@ -40,7 +40,7 @@ export class ExternalElementsDragAndDropData implements _.IDragAndDropData {
}
}
export class DesktopDragAndDropData implements _.IDragAndDropData {
export class DesktopDragAndDropData implements IDragAndDropData {
private types: any[];
private files: any[];
......@@ -50,15 +50,15 @@ export class DesktopDragAndDropData implements _.IDragAndDropData {
this.files = [];
}
public update(event: Mouse.DragMouseEvent): void {
if (event.dataTransfer.types) {
public update(dataTransfer: DataTransfer): void {
if (dataTransfer.types) {
this.types = [];
Array.prototype.push.apply(this.types, event.dataTransfer.types);
Array.prototype.push.apply(this.types, dataTransfer.types as any);
}
if (event.dataTransfer.files) {
if (dataTransfer.files) {
this.files = [];
Array.prototype.push.apply(this.files, event.dataTransfer.files);
Array.prototype.push.apply(this.files, dataTransfer.files as any);
this.files = this.files.filter(f => f.size || f.type);
}
......
......@@ -21,7 +21,7 @@ import { HeightMap, IViewItem } from 'vs/base/parts/tree/browser/treeViewModel';
import * as _ from 'vs/base/parts/tree/browser/tree';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Event, Emitter } from 'vs/base/common/event';
import { DataTransfers } from 'vs/base/browser/dnd';
import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd';
import { DefaultTreestyler } from './treeDefaults';
import { Delayer, timeout } from 'vs/base/common/async';
......@@ -410,8 +410,6 @@ export class TreeView extends HeightMap {
private static counter: number = 0;
private instance: number;
private static currentExternalDragAndDropData: _.IDragAndDropData = null;
private context: IViewContext;
private modelListeners: Lifecycle.IDisposable[];
private model: Model.TreeModel;
......@@ -438,7 +436,7 @@ export class TreeView extends HeightMap {
private isRefreshing = false;
private refreshingPreviousChildrenIds: { [id: string]: string[] } = {};
private currentDragAndDropData: _.IDragAndDropData;
private currentDragAndDropData: IDragAndDropData;
private currentDropElement: any;
private currentDropElementReaction: _.IDragOverReaction;
private currentDropTarget: ViewItem;
......@@ -1399,7 +1397,7 @@ export class TreeView extends HeightMap {
}
this.currentDragAndDropData = new dnd.ElementsDragAndDropData(elements);
TreeView.currentExternalDragAndDropData = new dnd.ExternalElementsDragAndDropData(elements);
StaticDND.CurrentDragAndDropData = new dnd.ExternalElementsDragAndDropData(elements);
this.context.dnd.onDragStart(this.context.tree, this.currentDragAndDropData, new Mouse.DragMouseEvent(e));
}
......@@ -1482,8 +1480,8 @@ export class TreeView extends HeightMap {
if (!this.currentDragAndDropData) {
// just started dragging
if (TreeView.currentExternalDragAndDropData) {
this.currentDragAndDropData = TreeView.currentExternalDragAndDropData;
if (StaticDND.CurrentDragAndDropData) {
this.currentDragAndDropData = StaticDND.CurrentDragAndDropData;
} else {
if (!event.dataTransfer.types) {
return false;
......@@ -1493,7 +1491,7 @@ export class TreeView extends HeightMap {
}
}
this.currentDragAndDropData.update(event);
this.currentDragAndDropData.update((event.browserEvent as DragEvent).dataTransfer);
let element: any;
let item: Model.Item = viewItem.model;
......@@ -1581,7 +1579,7 @@ export class TreeView extends HeightMap {
if (this.currentDropElement) {
let event = new Mouse.DragMouseEvent(e);
event.preventDefault();
this.currentDragAndDropData.update(event);
this.currentDragAndDropData.update((event.browserEvent as DragEvent).dataTransfer);
this.context.dnd.drop(this.context.tree, this.currentDragAndDropData, this.currentDropElement, event);
this.onDragEnd(e);
}
......@@ -1598,7 +1596,7 @@ export class TreeView extends HeightMap {
this.cancelDragAndDropScrollInterval();
this.currentDragAndDropData = null;
TreeView.currentExternalDragAndDropData = null;
StaticDND.CurrentDragAndDropData = undefined;
this.currentDropElement = null;
this.currentDropTarget = null;
this.dragAndDropMouseY = null;
......
......@@ -56,7 +56,14 @@
height: 100%;
}
.monaco-editor .suggest-widget .monaco-list {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
/** Styles for each row in the list element **/
......@@ -68,6 +75,8 @@
background-repeat: no-repeat;
background-position: 2px 2px;
white-space: nowrap;
cursor: pointer;
touch-action: none;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents {
......
......@@ -11,7 +11,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass } from 'vs/base/browser/dom';
import { IListVirtualDelegate, IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
......@@ -470,9 +470,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.list = new List(this.listElement, this, [renderer], {
useShadows: false,
selectOnMouseDown: true,
focusOnMouseDown: false,
openController: { shouldOpen: () => false }
openController: { shouldOpen: () => false },
mouseSupport: false
});
this.toDispose = [
......@@ -482,6 +481,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}),
themeService.onThemeChange(t => this.onThemeChange(t)),
editor.onDidLayoutChange(() => this.onEditorLayoutChange()),
this.list.onMouseDown(e => this.onListMouseDown(e)),
this.list.onSelectionChange(e => this.onListSelection(e)),
this.list.onFocusChange(e => this.onListFocus(e)),
this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())
......@@ -510,18 +510,29 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
}
private onListMouseDown(e: IListMouseEvent<CompletionItem>): void {
if (typeof e.element === 'undefined' || typeof e.index === 'undefined') {
return;
}
this.select(e.element, e.index);
}
private onListSelection(e: IListEvent<CompletionItem>): void {
if (!e.elements.length) {
return;
}
this.select(e.elements[0], e.indexes[0]);
}
private select(item: CompletionItem, index: number): void {
const completionModel = this.completionModel;
if (!completionModel) {
return;
}
const item = e.elements[0];
const index = e.indexes[0];
item.resolve(CancellationToken.None).then(() => {
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.completion.label));
......
......@@ -162,22 +162,24 @@ class WorkbenchOpenController implements IOpenController {
}
function toWorkbenchListOptions<T>(options: IListOptions<T>, configurationService: IConfigurationService, keybindingService: IKeybindingService): IListOptions<T> {
const result = { ...options };
if (options.multipleSelectionSupport !== false && !options.multipleSelectionController) {
options.multipleSelectionController = new MultipleSelectionController(configurationService);
result.multipleSelectionController = new MultipleSelectionController(configurationService);
}
options.openController = new WorkbenchOpenController(configurationService, options.openController);
result.openController = new WorkbenchOpenController(configurationService, options.openController);
if (options.keyboardNavigationLabelProvider) {
const tlp = options.keyboardNavigationLabelProvider;
options.keyboardNavigationLabelProvider = {
result.keyboardNavigationLabelProvider = {
getKeyboardNavigationLabel(e) { return tlp.getKeyboardNavigationLabel(e); },
mightProducePrintableCharacter(e) { return keybindingService.mightProducePrintableCharacter(e); }
};
}
return options;
return result;
}
let sharedListStyleSheet: HTMLStyleElement;
......@@ -234,7 +236,6 @@ export class WorkbenchList<T> extends List<T> {
super(container, delegate, renderers,
{
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...toWorkbenchListOptions(options, configurationService, keybindingService)
......@@ -310,7 +311,6 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
super(container, delegate, renderers,
{
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...toWorkbenchListOptions(options, configurationService, keybindingService)
......@@ -903,7 +903,6 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
) {
super(container, delegate, renderers, {
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...toWorkbenchListOptions(options, configurationService, keybindingService)
......@@ -980,7 +979,6 @@ export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<T
) {
super(container, delegate, renderers, dataSource, {
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...toWorkbenchListOptions(options, configurationService, keybindingService)
......@@ -1052,7 +1050,6 @@ export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends Async
) {
super(container, delegate, renderers, dataSource, {
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...toWorkbenchListOptions(options, configurationService, keybindingService)
......
......@@ -15,12 +15,12 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { DataTransfers } from 'vs/base/browser/dnd';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { MIME_BINARY } from 'vs/base/common/mime';
import { ITree, IDragAndDropData } from 'vs/base/parts/tree/browser/tree';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { isWindows } from 'vs/base/common/platform';
import { coalesce } from 'vs/base/common/arrays';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......
......@@ -23,7 +23,7 @@ import { IFilesConfiguration, SortOrder } from 'vs/workbench/parts/files/common/
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationError, FileOperationResult, IFileService, FileKind } from 'vs/platform/files/common/files';
import { DuplicateFileAction, AddFilesAction, IEditableData, IFileViewletState, FileCopiedContext } from 'vs/workbench/parts/files/electron-browser/fileActions';
import { IDataSource, ITree, IAccessibilityProvider, IRenderer, ContextMenuEvent, ISorter, IFilter, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY, DRAG_OVER_ACCEPT_BUBBLE_UP, DRAG_OVER_ACCEPT_BUBBLE_UP_COPY, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import { IDataSource, ITree, IAccessibilityProvider, IRenderer, ContextMenuEvent, ISorter, IFilter, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY, DRAG_OVER_ACCEPT_BUBBLE_UP, DRAG_OVER_ACCEPT_BUBBLE_UP_COPY, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import { DesktopDragAndDropData, ExternalElementsDragAndDropData } from 'vs/base/parts/tree/browser/treeDnd';
import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { ExplorerItem, NewStatPlaceholder, Model } from 'vs/workbench/parts/files/common/explorerModel';
......@@ -46,7 +46,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common
import { extractResources, SimpleFileResourceDragAndDrop, CodeDataTransfers, fillResourceDataTransfers } from 'vs/workbench/browser/dnd';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { DataTransfers } from 'vs/base/browser/dnd';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { Schemas } from 'vs/base/common/network';
import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
import { rtrim } from 'vs/base/common/strings';
......
......@@ -222,8 +222,7 @@ export class OpenEditorsView extends ViewletPanel {
new EditorGroupRenderer(this.keybindingService, this.instantiationService, this.editorGroupService),
new OpenEditorRenderer(this.listLabels, getSelectedElements, this.instantiationService, this.keybindingService, this.configurationService, this.editorGroupService)
], {
identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() },
selectOnMouseDown: false /* disabled to better support DND */
identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() }
}) as WorkbenchList<OpenEditor | IEditorGroup>;
this.disposables.push(this.list);
this.disposables.push(this.listLabels);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册