提交 7b50be29 编写于 作者: J Joao Moreno

tree: dnd bubble behavior

上级 cd6769c7
......@@ -87,7 +87,7 @@ export const ListDragOverReactions = {
export interface IListDragAndDrop<T> {
getDragURI(element: T): string | null;
getDragLabel?(elements: T[]): string;
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;
......
......@@ -611,11 +611,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri]));
if (event.dataTransfer.setDragImage) {
let label: string;
let label: string | undefined;
if (this.dnd.getDragLabel) {
label = this.dnd.getDragLabel(elements);
} else {
}
if (typeof label === 'undefined') {
label = String(elements.length);
}
......@@ -721,6 +723,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
private onDragLeave(): void {
this.onDragLeaveTimeout.dispose();
this.onDragLeaveTimeout = DOM.timeout(() => this.clearDragOverFeedback(), 100);
}
......@@ -750,6 +753,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private clearDragOverFeedback(): void {
this.currentDragFeedback = undefined;
this.currentDragFeedbackDisposable.dispose();
this.currentDragFeedbackDisposable = Disposable.None;
}
// DND scroll top animation
......
......@@ -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, IListDragAndDrop } 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[];
......@@ -940,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;
......@@ -1051,14 +1083,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
const viewOptions: IListViewOptions<T> = {
...options,
dnd: options.dnd && {
...options.dnd,
getDragElements: element => {
const selection = this.getSelectedElements();
const elements = selection.indexOf(element) > -1 ? selection : [element];
return elements;
}
}
dnd: options.dnd && new ListViewDragAndDrop(this, options.dnd)
};
this.view = new ListView(container, virtualDelegate, renderers, viewOptions);
......
......@@ -6,15 +6,71 @@
import 'vs/css!./media/tree';
import { IDisposable, dispose } 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 { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
import { append, $, toggleClass } 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, ITreeDragAndDrop } 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>> {
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, force = false): boolean | IListDragOverReaction {
const result = this.dnd.onDragOver(data, targetNode && targetNode.element, targetIndex, originalEvent);
if (typeof targetNode === 'undefined') {
return result;
}
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
if (force) {
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, true);
}
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,23 +78,7 @@ function asListOptions<T, TFilterData>(options?: IAbstractTreeOptions<T, TFilter
return options.identityProvider!.getId(el.element);
}
},
dnd: options.dnd && {
getDragURI(node) {
return options.dnd!.getDragURI(node.element);
},
getDragLabel: options.dnd!.getDragLabel && ((nodes) => {
return options.dnd!.getDragLabel!(nodes.map(node => node.element));
}),
onDragStart(data, originalEvent) {
return options.dnd!.onDragStart(data, originalEvent);
},
onDragOver(data, targetNode, targetIndex, originalEvent) {
return options.dnd!.onDragOver(data, targetNode && targetNode.element, targetIndex, originalEvent);
},
drop(data, targetNode, targetIndex, originalEvent) {
return options.dnd!.drop(data, targetNode && targetNode.element, targetIndex, originalEvent);
}
},
dnd: options.dnd && new TreeNodeListDragAndDrop(modelProvider, options.dnd),
multipleSelectionController: options.multipleSelectionController && {
isSelectionSingleChangeEvent(e) {
return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
......@@ -239,7 +279,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;
......
......@@ -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);
......
......@@ -101,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;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册