listWidget.ts 6.2 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./list';
J
Joao Moreno 已提交
7
import { IDisposable, dispose, disposeAll } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
8
import { isNumber } from 'vs/base/common/types';
J
Joao Moreno 已提交
9
import * as DOM from 'vs/base/browser/dom';
J
Joao Moreno 已提交
10 11
import Event, { Emitter, mapEvent } from 'vs/base/common/event';
import { IDelegate, IRenderer, IListMouseEvent, IFocusChangeEvent, ISelectionChangeEvent } from './list';
J
Joao Moreno 已提交
12 13 14 15 16 17 18
import { ListView } from './listView';

interface ITraitTemplateData<D> {
	container: HTMLElement;
	data: D;
}

J
Joao Moreno 已提交
19 20 21 22
interface ITraitChangeEvent {
	indexes: number[];
}

J
Joao Moreno 已提交
23 24 25 26 27
class TraitRenderer<T, D> implements IRenderer<T, ITraitTemplateData<D>>
{
	private elements: { [id: string]: T };

	constructor(
J
Joao Moreno 已提交
28
		private controller: Trait,
J
Joao Moreno 已提交
29 30 31 32 33 34 35 36 37 38 39 40
		private renderer: IRenderer<T,D>
	) {}

	public get templateId(): string {
		return this.renderer.templateId;
	}

	renderTemplate(container: HTMLElement): ITraitTemplateData<D> {
		const data = this.renderer.renderTemplate(container);
		return { container, data };
	}

J
Joao Moreno 已提交
41
	renderElement(element: T, index: number, templateData: ITraitTemplateData<D>): void {
J
Joao Moreno 已提交
42
		DOM.toggleClass(templateData.container, this.controller.trait, this.controller.contains(index));
J
Joao Moreno 已提交
43
		this.renderer.renderElement(element, index, templateData.data);
J
Joao Moreno 已提交
44 45 46 47 48 49 50
	}

	disposeTemplate(templateData: ITraitTemplateData<D>): void {
		return this.renderer.disposeTemplate(templateData.data);
	}
}

J
Joao Moreno 已提交
51
class Trait implements IDisposable {
J
Joao Moreno 已提交
52

J
Joao Moreno 已提交
53
	private indexes: number[];
J
Joao Moreno 已提交
54

J
Joao Moreno 已提交
55 56 57
	private _onChange = new Emitter<ITraitChangeEvent>();
	get onChange() { return this._onChange.event; }

J
Joao Moreno 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
	constructor(private _trait: string) {
		this.indexes = [];
	}

	splice(start: number, deleteCount: number, insertCount: number): void {
		const diff = insertCount - deleteCount;
		const end = start + deleteCount;
		const indexes = [];

		for (const index of indexes) {
			if (index >= start && index < end) {
				continue;
			}

			indexes.push(index > start ? index + diff : index);
		}

		this.indexes = indexes;
J
Joao Moreno 已提交
76
		this._onChange.fire({ indexes });
J
Joao Moreno 已提交
77 78 79 80 81 82
	}

	get trait(): string {
		return this._trait;
	}

J
Joao Moreno 已提交
83
	set(...indexes: number[]): number[] {
J
Joao Moreno 已提交
84 85
		const result = this.indexes;
		this.indexes = indexes;
J
Joao Moreno 已提交
86
		this._onChange.fire({ indexes });
J
Joao Moreno 已提交
87
		return result;
J
Joao Moreno 已提交
88 89
	}

J
Joao Moreno 已提交
90 91 92 93
	get(): number[] {
		return this.indexes;
	}

J
Joao Moreno 已提交
94 95 96
	add(index: number): void {
		if (this.contains(index)) {
			return;
J
Joao Moreno 已提交
97
		}
J
Joao Moreno 已提交
98 99

		this.indexes.push(index);
J
Joao Moreno 已提交
100
		this._onChange.fire({ indexes: this.indexes });
J
Joao Moreno 已提交
101 102
	}

J
Joao Moreno 已提交
103 104
	remove(index: number): void {
		this.indexes = this.indexes.filter(i => i === index);
J
Joao Moreno 已提交
105
		this._onChange.fire({ indexes: this.indexes });
J
Joao Moreno 已提交
106 107
	}

J
Joao Moreno 已提交
108 109
	contains(index: number): boolean {
		return this.indexes.some(i => i === index);
J
Joao Moreno 已提交
110 111
	}

J
Joao Moreno 已提交
112
	wrapRenderer<T, D>(renderer: IRenderer<T, D>): IRenderer<T, ITraitTemplateData<D>> {
J
Joao Moreno 已提交
113 114
		return new TraitRenderer<T, D>(this, renderer);
	}
J
Joao Moreno 已提交
115 116 117 118 119

	dispose() {
		this.indexes = null;
		this._onChange = dispose(this._onChange);
	}
J
Joao Moreno 已提交
120 121
}

J
Joao Moreno 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
class Controller<T> implements IDisposable {

	private toDispose: IDisposable[];

	constructor(
		private list: List<T>,
		private view: ListView<T>
	) {
		this.toDispose = [];
		this.toDispose.push(view.addListener('click', e => this.onClick(e)));
	}

	private onClick(e: IListMouseEvent<T>) {
		this.list.setSelection(e.index);
	}

	dispose() {
		this.toDispose = disposeAll(this.toDispose);
	}
}

J
Joao Moreno 已提交
143 144
export class List<T> implements IDisposable {

J
Joao Moreno 已提交
145 146
	private focus: Trait;
	private selection: Trait;
J
Joao Moreno 已提交
147
	private view: ListView<T>;
J
Joao Moreno 已提交
148
	private controller: Controller<T>;
J
Joao Moreno 已提交
149

J
Joao Moreno 已提交
150 151 152 153 154 155 156 157 158 159 160 161 162 163
	get onFocusChange(): Event<IFocusChangeEvent<T>> {
		return mapEvent(this.focus.onChange, e => ({
			elements: e.indexes.map(i => this.view.element(i)),
			indexes: e.indexes
		}));
	}

	get onSelectionChange(): Event<ISelectionChangeEvent<T>> {
		return mapEvent(this.selection.onChange, e => ({
			elements: e.indexes.map(i => this.view.element(i)),
			indexes: e.indexes
		}));
	}

J
Joao Moreno 已提交
164 165 166
	constructor(
		container: HTMLElement,
		delegate: IDelegate<T>,
J
Joao Moreno 已提交
167
		renderers: IRenderer<T, any>[]
J
Joao Moreno 已提交
168
	) {
J
Joao Moreno 已提交
169 170
		this.focus = new Trait('focused');
		this.selection = new Trait('selected');
J
Joao Moreno 已提交
171 172 173 174 175 176 177 178

		renderers = renderers.map(r => {
			r = this.focus.wrapRenderer(r);
			r = this.selection.wrapRenderer(r);
			return r;
		});

		this.view = new ListView(container, delegate, renderers);
J
Joao Moreno 已提交
179
		this.controller = new Controller(this, this.view);
J
Joao Moreno 已提交
180 181 182
	}

	splice(start: number, deleteCount: number, ...elements: T[]): void {
J
Joao Moreno 已提交
183 184 185
		this.focus.splice(start, deleteCount, elements.length);
		this.selection.splice(start, deleteCount, elements.length);
		this.view.splice(start, deleteCount, ...elements);
J
Joao Moreno 已提交
186 187 188 189 190 191
	}

	get length(): number {
		return this.view.length;
	}

J
Joao Moreno 已提交
192 193 194 195
	get contentHeight(): number {
		return this.view.getScrollHeight();
	}

J
Joao Moreno 已提交
196 197 198 199
	layout(height?: number): void {
		this.view.layout(height);
	}

J
Joao Moreno 已提交
200
	setSelection(...indexes: number[]): void {
J
Joao Moreno 已提交
201
		indexes = indexes.concat(this.selection.set(...indexes));
J
Joao Moreno 已提交
202
		indexes.forEach(i => this.view.splice(i, 1, this.view.element(i)));
J
Joao Moreno 已提交
203 204
	}

J
Joao Moreno 已提交
205
	setFocus(...indexes: number[]): void {
J
Joao Moreno 已提交
206
		indexes = indexes.concat(this.focus.set(...indexes));
J
Joao Moreno 已提交
207
		indexes.forEach(i => this.view.splice(i, 1, this.view.element(i)));
J
Joao Moreno 已提交
208 209
	}

J
Joao Moreno 已提交
210 211 212 213
	getFocus(): T[] {
		return this.focus.get().map(i => this.view.element(i));
	}

J
Joao Moreno 已提交
214 215 216 217 218 219 220 221 222 223
	reveal(index: number, relativeTop?: number): void {
		const scrollTop = this.view.getScrollTop();
		const elementTop = this.view.elementTop(index);
		const elementHeight = this.view.elementHeight(index);

		if (isNumber(relativeTop)) {
			relativeTop = relativeTop < 0 ? 0 : relativeTop;
			relativeTop = relativeTop > 1 ? 1 : relativeTop;

			// y = mx + b
J
Joao Moreno 已提交
224
			const m = elementHeight - this.view.height;
J
Joao Moreno 已提交
225 226
			this.view.setScrollTop(m * relativeTop + elementTop);
		} else {
J
Joao Moreno 已提交
227 228
			const viewItemBottom = elementTop + elementHeight;
			const wrapperBottom = scrollTop + this.view.height;
J
Joao Moreno 已提交
229 230 231 232 233 234 235 236 237

			if (elementTop < scrollTop) {
				this.view.setScrollTop(elementTop);
			} else if (viewItemBottom >= wrapperBottom) {
				this.view.setScrollTop(viewItemBottom - this.view.height);
			}
		}
	}

J
Joao Moreno 已提交
238 239
	dispose(): void {
		this.view = dispose(this.view);
J
Joao Moreno 已提交
240 241
		this.focus = dispose(this.focus);
		this.selection = dispose(this.selection);
J
Joao Moreno 已提交
242 243
	}
}