提交 65bdaf3a 编写于 作者: M Maxime Quandalle 提交者: Benjamin Pasero

Sash double clicks (#4702)

* Support double click on sashes

We had to implement a slight change in the sash drag "hack", by removing the
full cover transparent overlay (that was preventing clicks events from being
fired) and replacing it by a CSS `cursor` rule on the document.body.

The ideal solution for a clean dragging events implementation would be to use
the `Element.setCapture` API that is unfortunately not available in Chrome.

* Center the split editor frontier by double-clicking on it

* Reset the bottom panel size by double-clicking on it

* Implement sash reset on diff editors

* Fix a bug with DOM measurement

The calculus was plain wrong as showed in https://github.com/winjs/winjs/issues/1621
This was probably unotified since this utility function wasn't used in the code base.

* Implement sidebar sash optimal resizing

Fixes #4660

* Abstract `getLargestChildWidth` into a DOM method

This commit also moves the `getOptimalWidth` method from the `Composite` to the
`Viewlet` component. This partially addresses
https://github.com/Microsoft/vscode/pull/4702#issuecomment-207813241

* Calculate the sidebar optimal width correctly in case no folder is open
上级 b8333069
......@@ -637,46 +637,34 @@ export function getTotalHeight(element: HTMLElement): number {
return element.offsetHeight + margin;
}
// Adapted from WinJS
// Gets the left coordinate of the specified element relative to the specified parent.
export function getRelativeLeft(element: HTMLElement, parent: HTMLElement): number {
if (element === null) {
return 0;
}
let left = element.offsetLeft;
let e = <HTMLElement>element.parentNode;
while (e !== null) {
left -= e.offsetLeft;
if (e === parent) {
break;
}
e = <HTMLElement>e.parentNode;
}
return left;
let elementPosition = getTopLeftOffset(element);
let parentPosition = getTopLeftOffset(parent);
return elementPosition.left - parentPosition.left;
}
// Adapted from WinJS
// Gets the top coordinate of the element relative to the specified parent.
export function getRelativeTop(element: HTMLElement, parent: HTMLElement): number {
if (element === null) {
return 0;
}
let top = element.offsetTop;
let e = <HTMLElement>element.parentNode;
while (e !== null) {
top -= e.offsetTop;
if (e === parent) {
break;
}
e = <HTMLElement>e.parentNode;
}
let elementPosition = getTopLeftOffset(element);
let parentPosition = getTopLeftOffset(parent);
return parentPosition.top - elementPosition.top;
}
return top;
export function getLargestChildWidth(parent: HTMLElement, children: HTMLElement[]): number {
let childWidths = children.map((child) => {
return getTotalWidth(child) + getRelativeLeft(child, parent) || 0;
});
let maxWidth = Math.max(...childWidths);
return maxWidth;
}
// ----------------------------------------------------------------------------------------
......
......@@ -23,4 +23,12 @@
.monaco-sash.disabled {
cursor: default;
}
.vertical-cursor-container * {
cursor: ew-resize !important;
}
.horizontal-cursor-container * {
cursor: ns-resize !important;
}
\ No newline at end of file
......@@ -63,11 +63,12 @@ export class Sash extends EventEmitter {
this.gesture = new Gesture(this.$e.getHTMLElement());
this.$e.on('mousedown', (e: MouseEvent) => { this.onMouseDown(e); });
this.$e.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { this.onMouseDown(e); });
this.$e.on(DOM.EventType.DBLCLICK, (e: MouseEvent) => { this.emit('reset', e); });
this.$e.on(EventType.Start, (e: GestureEvent) => { this.onTouchStart(e); });
this.orientation = options.orientation || Orientation.VERTICAL;
this.$e.addClass(this.orientation === Orientation.HORIZONTAL ? 'horizontal' : 'vertical');
this.$e.addClass(this.getOrientation());
this.size = options.baseSize || 5;
......@@ -91,6 +92,10 @@ export class Sash extends EventEmitter {
return this.$e.getHTMLElement();
}
private getOrientation(): 'horizontal' | 'vertical' {
return this.orientation === Orientation.HORIZONTAL ? 'horizontal' : 'vertical';
}
private onMouseDown(e: MouseEvent): void {
DOM.EventHelper.stop(e, false);
......@@ -112,17 +117,8 @@ export class Sash extends EventEmitter {
this.$e.addClass('active');
this.emit('start', startEvent);
let overlayDiv = $('div').style({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 1000000,
cursor: this.orientation === Orientation.VERTICAL ? 'ew-resize' : 'ns-resize'
});
let $window = $(window);
let containerCssClass = `${this.getOrientation()}-cursor-container`;
let lastCurrentX = startX;
let lastCurrentY = startY;
......@@ -148,10 +144,10 @@ export class Sash extends EventEmitter {
this.emit('end');
$window.off('mousemove');
overlayDiv.destroy();
document.body.classList.remove(containerCssClass);
});
overlayDiv.appendTo(document.body);
document.body.classList.add(containerCssClass);
}
private onTouchStart(event: GestureEvent): void {
......
......@@ -1327,9 +1327,10 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd
this._sash.disable();
}
this._sash.on('start', () => this._onSashDragStart());
this._sash.on('change', (e: ISashEvent) => this._onSashDrag(e));
this._sash.on('end', () => this._onSashDragEnd());
this._sash.on('start', () => this.onSashDragStart());
this._sash.on('change', (e: ISashEvent) => this.onSashDrag(e));
this._sash.on('end', () => this.onSashDragEnd());
this._sash.on('reset', () => this.onSashReset());
}
public dispose(): void {
......@@ -1378,11 +1379,11 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd
return this._sashPosition;
}
private _onSashDragStart(): void {
private onSashDragStart(): void {
this._startSashPosition = this._sashPosition;
}
private _onSashDrag(e:ISashEvent): void {
private onSashDrag(e:ISashEvent): void {
var w = this._dataSource.getWidth();
var contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH;
var sashPosition = this.layout((this._startSashPosition + (e.currentX - e.startX)) / contentWidth);
......@@ -1392,7 +1393,13 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd
this._dataSource.relayoutEditors();
}
private _onSashDragEnd(): void {
private onSashDragEnd(): void {
this._sash.layout();
}
private onSashReset(): void {
this._sashRatio = 0.5;
this._dataSource.relayoutEditors();
this._sash.layout();
}
......
......@@ -12,6 +12,7 @@ import {Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProv
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {IPartService, Position} from 'vs/workbench/services/part/common/partService';
import {IWorkspaceContextService} from 'vs/workbench/services/workspace/common/contextService';
import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService';
import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage';
import {IContextViewService} from 'vs/platform/contextview/browser/contextView';
import {IEventService} from 'vs/platform/event/common/event';
......@@ -94,6 +95,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IPartService private partService: IPartService,
@IViewletService private viewletService: IViewletService,
@IThemeService themeService: IThemeService
) {
this.parent = parent;
......@@ -190,7 +192,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
let dragCompensation = DEFAULT_MIN_PANEL_PART_HEIGHT - HIDE_PANEL_HEIGHT_THRESHOLD;
this.partService.setPanelHidden(true);
startY = Math.min(this.sidebarHeight - this.computedStyles.statusbar.height, e.currentY + dragCompensation);
this.panelHeight = this.startPanelHeight; // when restoring panel, restore to the panel width we started from
this.panelHeight = this.startPanelHeight; // when restoring panel, restore to the panel height we started from
}
// Otherwise size the panel accordingly
......@@ -217,9 +219,26 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
this.sashX.addListener('end', () => {
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
});
this.sashY.addListener('end', () => {
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
});
this.sashY.addListener('reset', () => {
this.panelHeight = DEFAULT_MIN_PANEL_PART_HEIGHT;
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
this.partService.setPanelHidden(false);
this.layout();
});
this.sashX.addListener('reset', () => {
let activeViewlet = this.viewletService.getActiveViewlet();
let optimalWidth = activeViewlet && activeViewlet.getOptimalWidth();
this.sidebarWidth = Math.max(DEFAULT_MIN_PART_WIDTH, optimalWidth || 0);
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
this.partService.setSideBarHidden(false);
this.layout();
});
}
private onEditorInputChanging(e: EditorEvent): void {
......
......@@ -72,6 +72,7 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas
this.sash.addListener('start', () => this.onSashDragStart());
this.sash.addListener('change', (e: ISashEvent) => this.onSashDrag(e));
this.sash.addListener('end', () => this.onSashDragEnd());
this.sash.addListener('reset', () => this.onSashReset());
// Right Container for Binary
let rightBinaryContainerElement = document.createElement('div');
......@@ -199,6 +200,12 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas
this.sash.layout();
}
private onSashReset(): void {
this.leftContainerWidth = this.dimension.width / 2;
this.layoutContainers();
this.sash.layout();
}
public getVerticalSashTop(sash: Sash): number {
return 0;
}
......
......@@ -728,6 +728,7 @@ export class SideBySideEditorControl extends EventEmitter implements IVerticalSa
this.leftSash.addListener('start', () => this.onLeftSashDragStart());
this.leftSash.addListener('change', (e: ISashEvent) => this.onLeftSashDrag(e));
this.leftSash.addListener('end', () => this.onLeftSashDragEnd());
this.leftSash.addListener('reset', () => this.onLeftSashReset());
this.leftSash.hide();
// Center Container
......@@ -738,6 +739,7 @@ export class SideBySideEditorControl extends EventEmitter implements IVerticalSa
this.rightSash.addListener('start', () => this.onRightSashDragStart());
this.rightSash.addListener('change', (e: ISashEvent) => this.onRightSashDrag(e));
this.rightSash.addListener('end', () => this.onRightSashDragEnd());
this.rightSash.addListener('reset', () => this.onRightSashReset());
this.rightSash.hide();
// Right Container
......@@ -1212,6 +1214,14 @@ export class SideBySideEditorControl extends EventEmitter implements IVerticalSa
this.editorActionsToolbar[position].setActions([], [])();
}
private centerSash(a: Position, b: Position): void {
let sumWidth = this.containerWidth[a] + this.containerWidth[b];
let meanWidth = sumWidth / 2;
this.containerWidth[a] = meanWidth;
this.containerWidth[b] = sumWidth - meanWidth;
this.layoutContainers();
}
private onLeftSashDragStart(): void {
this.startLeftContainerWidth = this.containerWidth[Position.LEFT];
}
......@@ -1301,6 +1311,11 @@ export class SideBySideEditorControl extends EventEmitter implements IVerticalSa
this.focusNextNonMinimized();
}
private onLeftSashReset(): void {
this.centerSash(Position.LEFT, Position.CENTER);
this.leftSash.layout();
}
private onRightSashDragStart(): void {
this.startRightContainerWidth = this.containerWidth[Position.RIGHT];
}
......@@ -1358,6 +1373,11 @@ export class SideBySideEditorControl extends EventEmitter implements IVerticalSa
this.focusNextNonMinimized();
}
private onRightSashReset(): void {
this.centerSash(Position.CENTER, Position.RIGHT);
this.rightSash.layout();
}
public getVerticalSashTop(sash: Sash): number {
return 0;
}
......
......@@ -78,7 +78,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
}
public getActiveViewlet(): IViewlet {
return this.getActiveComposite();
return <IViewlet>this.getActiveComposite();
}
public getLastActiveViewletId(): string {
......
......@@ -25,7 +25,11 @@ import {IContextMenuService} from 'vs/platform/contextview/browser/contextView';
import {IMessageService} from 'vs/platform/message/common/message';
import {StructuredSelection} from 'vs/platform/selection/common/selection';
export abstract class Viewlet extends Composite implements IViewlet { }
export abstract class Viewlet extends Composite implements IViewlet {
public getOptimalWidth(): number {
return null;
}
}
/**
* Helper subtype of viewlet for those that use a tree inside.
......
......@@ -5,4 +5,9 @@
import {IComposite} from 'vs/workbench/common/composite';
export interface IViewlet extends IComposite { }
export interface IViewlet extends IComposite {
/**
* Returns the minimal width needed to avoid any content horizontal truncation
*/
getOptimalWidth(): number;
}
......@@ -175,6 +175,15 @@ export class ExplorerViewlet extends Viewlet {
return this.actionRunner;
}
public getOptimalWidth(): number {
let additionalMargin = 16;
let workingFilesViewWidth = this.getWorkingFilesView().getOptimalWidth();
let explorerView = this.getExplorerView();
let explorerViewWidth = explorerView ? explorerView.getOptimalWidth() : 0;
let optimalWidth = Math.max(workingFilesViewWidth, explorerViewWidth);
return optimalWidth + additionalMargin;
}
public shutdown(): void {
this.views.forEach((view) => view.shutdown());
......
......@@ -24,7 +24,7 @@ import {FileEditorInput} from 'vs/workbench/parts/files/browser/editors/fileEdit
import {FileDragAndDrop, FileFilter, FileSorter, FileController, FileRenderer, FileDataSource, FileViewletState, FileAccessibilityProvider} from 'vs/workbench/parts/files/browser/views/explorerViewer';
import lifecycle = require('vs/base/common/lifecycle');
import {IEditor} from 'vs/platform/editor/common/editor';
import DOM = require('vs/base/browser/dom');
import * as DOM from 'vs/base/browser/dom';
import {CollapseAction, CollapsibleViewletView} from 'vs/workbench/browser/viewlet';
import {FileStat} from 'vs/workbench/parts/files/common/explorerViewModel';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
......@@ -348,6 +348,12 @@ export class ExplorerView extends CollapsibleViewletView {
return this.explorerViewer;
}
public getOptimalWidth(): number {
let parentNode = this.explorerViewer.getHTMLElement();
let childNodes = [].slice.call(parentNode.querySelectorAll('.explorer-item-label > a'));
return DOM.getLargestChildWidth(parentNode, childNodes);
}
private onLocalFileChange(e: LocalFileChangeEvent): void {
let modelElement: FileStat;
let parent: FileStat;
......
......@@ -13,7 +13,7 @@ import {IAction, IActionRunner} from 'vs/base/common/actions';
import workbenchEditorCommon = require('vs/workbench/common/editor');
import {CollapsibleState} from 'vs/base/browser/ui/splitview/splitview';
import {IWorkingFileEntry, IWorkingFilesModel, IWorkingFileModelChangeEvent, LocalFileChangeEvent, EventType as FileEventType, IFilesConfiguration, ITextFileService, AutoSaveMode} from 'vs/workbench/parts/files/common/files';
import dom = require('vs/base/browser/dom');
import * as DOM from 'vs/base/browser/dom';
import {IDisposable} from 'vs/base/common/lifecycle';
import errors = require('vs/base/common/errors');
import {EventType as WorkbenchEventType, UntitledEditorEvent, EditorEvent} from 'vs/workbench/common/events';
......@@ -82,7 +82,7 @@ export class WorkingFilesView extends AdaptiveCollapsibleViewletView {
public renderBody(container: HTMLElement): void {
this.treeContainer = super.renderViewTree(container);
dom.addClass(this.treeContainer, 'explorer-working-files');
DOM.addClass(this.treeContainer, 'explorer-working-files');
this.createViewer($(this.treeContainer));
}
......@@ -303,6 +303,12 @@ export class WorkingFilesView extends AdaptiveCollapsibleViewletView {
return this.tree;
}
public getOptimalWidth():number {
let parentNode = this.tree.getHTMLElement();
let childNodes = [].slice.call(parentNode.querySelectorAll('.monaco-file-label > .file-name'));
return DOM.getLargestChildWidth(parentNode, childNodes);
}
public shutdown(): void {
this.settings[WorkingFilesView.MEMENTO_COLLAPSED] = (this.state === CollapsibleState.COLLAPSED);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册