Skip to content

Commit

Permalink
docs(sash): 📝 add documentation for the Sash widget
Browse files Browse the repository at this point in the history
Related-to: #124652
  • Loading branch information
joaomoreno committed Nov 10, 2021
1 parent 2b03df8 commit e7ea07d
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 51 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"splitview",
"table",
"list",
"git"
"git",
"sash"
]
}
224 changes: 182 additions & 42 deletions src/vs/base/browser/ui/sash/sash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,39 @@ import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecy
import { isMacintosh } from 'vs/base/common/platform';
import 'vs/css!./sash';

/**
* Allow the sashes to be visible at runtime.
* @remark Use for development purposes only.
*/
let DEBUG = false;
// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this

export interface ISashLayoutProvider { }

export interface IVerticalSashLayoutProvider extends ISashLayoutProvider {
/**
* A vertical sash layout provider provides position and height for a sash.
*/
export interface IVerticalSashLayoutProvider {
getVerticalSashLeft(sash: Sash): number;
getVerticalSashTop?(sash: Sash): number;
getVerticalSashHeight?(sash: Sash): number;
}

export interface IHorizontalSashLayoutProvider extends ISashLayoutProvider {
/**
* A vertical sash layout provider provides position and width for a sash.
*/
export interface IHorizontalSashLayoutProvider {
getHorizontalSashTop(sash: Sash): number;
getHorizontalSashLeft?(sash: Sash): number;
getHorizontalSashWidth?(sash: Sash): number;
}

type ISashLayoutProvider = IVerticalSashLayoutProvider | IHorizontalSashLayoutProvider;

export interface ISashEvent {
startX: number;
currentX: number;
startY: number;
currentY: number;
altKey: boolean;
readonly startX: number;
readonly currentX: number;
readonly startY: number;
readonly currentY: number;
readonly altKey: boolean;
}

export enum OrthogonalEdge {
Expand All @@ -46,10 +56,41 @@ export enum OrthogonalEdge {
}

export interface ISashOptions {

/**
* Whether a sash is horizontal or vertical.
*/
readonly orientation: Orientation;

/**
* The width or height of a vertical or horizontal sash, respectively.
*/
readonly size?: number;

/**
* A reference to another sash, perpendicular to this one, which
* aligns at the start of this one. A corner sash will be created
* automatically at that location.
*
* The start of a horizontal sash is its left-most position.
* The start of a vertical sash is its top-most position.
*/
readonly orthogonalStartSash?: Sash;

/**
* A reference to another sash, perpendicular to this one, which
* aligns at the end of this one. A corner sash will be created
* automatically at that location.
*
* The end of a horizontal sash is its right-most position.
* The end of a vertical sash is its bottom-most position.
*/
readonly orthogonalEndSash?: Sash;
readonly size?: number;

/**
* Provides a hint as to what mouse cursor to use whenever the user
* hovers over a corner sash provided by this and an orthogonal sash.
*/
readonly orthogonalEdge?: OrthogonalEdge;
}

Expand All @@ -67,9 +108,31 @@ export const enum Orientation {
}

export const enum SashState {

/**
* Disable any UI interaction.
*/
Disabled,
Minimum,
Maximum,

/**
* Allow dragging down or to the right, depending on the sash orientation.
*
* Some OSs allow customizing the mouse cursor differently whenever
* some resizable component can't be any smaller, but can be larger.
*/
AtMinimum,

/**
* Allow dragging up or to the left, depending on the sash orientation.
*
* Some OSs allow customizing the mouse cursor differently whenever
* some resizable component can't be any larger, but can be smaller.
*/
AtMaximum,

/**
* Enable dragging.
*/
Enabled
}

Expand Down Expand Up @@ -159,52 +222,101 @@ class OrthogonalPointerEventFactory implements IPointerEventFactory {
}
}

/**
* The {@link Sash} is the UI component which allows the user to resize other
* components. It's usually an invisible horizontal or vertical line which, when
* hovered, becomes highlighted and can be dragged along the perpendicular dimension
* to its direction.
*
* Features:
* - Touch event handling
* - Corner sash support
* - Hover with different mouse cursor support
* - Configurable hover size
* - Linked sash support, for 2x2 corner sashes
*/
export class Sash extends Disposable {

private el: HTMLElement;
private layoutProvider: ISashLayoutProvider;
private orientation!: Orientation;
private orientation: Orientation;
private size: number;
private hoverDelay = globalHoverDelay;
private hoverDelayer = this._register(new Delayer(this.hoverDelay));

private _state: SashState = SashState.Enabled;
private readonly onDidEnablementChange = this._register(new Emitter<SashState>());
private readonly _onDidStart = this._register(new Emitter<ISashEvent>());
private readonly _onDidChange = this._register(new Emitter<ISashEvent>());
private readonly _onDidReset = this._register(new Emitter<void>());
private readonly _onDidEnd = this._register(new Emitter<void>());
private readonly orthogonalStartSashDisposables = this._register(new DisposableStore());
private _orthogonalStartSash: Sash | undefined;
private readonly orthogonalStartDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalStartDragHandle: HTMLElement | undefined;
private readonly orthogonalEndSashDisposables = this._register(new DisposableStore());
private _orthogonalEndSash: Sash | undefined;
private readonly orthogonalEndDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalEndDragHandle: HTMLElement | undefined;

get state(): SashState { return this._state; }
get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }

/**
* The state of a sash defines whether it can be interacted with by the user
* as well as what mouse cursor to use, when hovered.
*/
set state(state: SashState) {
if (this._state === state) {
return;
}

this.el.classList.toggle('disabled', state === SashState.Disabled);
this.el.classList.toggle('minimum', state === SashState.Minimum);
this.el.classList.toggle('maximum', state === SashState.Maximum);
this.el.classList.toggle('minimum', state === SashState.AtMinimum);
this.el.classList.toggle('maximum', state === SashState.AtMaximum);

this._state = state;
this._onDidEnablementChange.fire(state);
this.onDidEnablementChange.fire(state);
}

private readonly _onDidEnablementChange = this._register(new Emitter<SashState>());
readonly onDidEnablementChange: Event<SashState> = this._onDidEnablementChange.event;

private readonly _onDidStart = this._register(new Emitter<ISashEvent>());
/**
* An event which fires whenever the user starts dragging this sash.
*/
readonly onDidStart: Event<ISashEvent> = this._onDidStart.event;

private readonly _onDidChange = this._register(new Emitter<ISashEvent>());
/**
* An event which fires whenever the user moves the mouse while
* dragging this sash.
*/
readonly onDidChange: Event<ISashEvent> = this._onDidChange.event;

private readonly _onDidReset = this._register(new Emitter<void>());
/**
* An event which fires whenever the user double clicks this sash.
*/
readonly onDidReset: Event<void> = this._onDidReset.event;

private readonly _onDidEnd = this._register(new Emitter<void>());
/**
* An event which fires whenever the user stops dragging this sash.
*/
readonly onDidEnd: Event<void> = this._onDidEnd.event;

/**
* A linked sash will be forwarded the same user interactions and events
* so it moves exactly the same way as this sash.
*
* Useful in 2x2 grids. Not meant for widespread usage.
*/
linkedSash: Sash | undefined = undefined;

private readonly orthogonalStartSashDisposables = this._register(new DisposableStore());
private _orthogonalStartSash: Sash | undefined;
private readonly orthogonalStartDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalStartDragHandle: HTMLElement | undefined;
get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the start of this one. A corner sash will be created
* automatically at that location.
*
* The start of a horizontal sash is its left-most position.
* The start of a vertical sash is its top-most position.
*/
set orthogonalStartSash(sash: Sash | undefined) {
this.orthogonalStartDragHandleDisposables.clear();
this.orthogonalStartSashDisposables.clear();
Expand All @@ -223,18 +335,22 @@ export class Sash extends Disposable {
}
};

this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange(onChange, this));
this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
onChange(sash.state);
}

this._orthogonalStartSash = sash;
}

private readonly orthogonalEndSashDisposables = this._register(new DisposableStore());
private _orthogonalEndSash: Sash | undefined;
private readonly orthogonalEndDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalEndDragHandle: HTMLElement | undefined;
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the end of this one. A corner sash will be created
* automatically at that location.
*
* The end of a horizontal sash is its right-most position.
* The end of a vertical sash is its bottom-most position.
*/

set orthogonalEndSash(sash: Sash | undefined) {
this.orthogonalEndDragHandleDisposables.clear();
this.orthogonalEndSashDisposables.clear();
Expand All @@ -253,15 +369,30 @@ export class Sash extends Disposable {
}
};

this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange(onChange, this));
this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
onChange(sash.state);
}

this._orthogonalEndSash = sash;
}

constructor(container: HTMLElement, layoutProvider: IVerticalSashLayoutProvider, options: ISashOptions);
constructor(container: HTMLElement, layoutProvider: IHorizontalSashLayoutProvider, options: ISashOptions);
/**
* Create a new vertical sash.
*
* @param container A DOM node to append the sash to.
* @param verticalLayoutProvider A vertical layout provider.
* @param options The options.
*/
constructor(container: HTMLElement, verticalLayoutProvider: IVerticalSashLayoutProvider, options: IVerticalSashOptions);

/**
* Create a new horizontal sash.
*
* @param container A DOM node to append the sash to.
* @param horizontalLayoutProvider A horizontal layout provider.
* @param options The options.
*/
constructor(container: HTMLElement, horizontalLayoutProvider: IHorizontalSashLayoutProvider, options: IHorizontalSashOptions);
constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions) {
super();

Expand Down Expand Up @@ -381,17 +512,17 @@ export class Sash extends Disposable {
if (isMultisashResize) {
cursor = 'all-scroll';
} else if (this.orientation === Orientation.HORIZONTAL) {
if (this.state === SashState.Minimum) {
if (this.state === SashState.AtMinimum) {
cursor = 's-resize';
} else if (this.state === SashState.Maximum) {
} else if (this.state === SashState.AtMaximum) {
cursor = 'n-resize';
} else {
cursor = isMacintosh ? 'row-resize' : 'ns-resize';
}
} else {
if (this.state === SashState.Minimum) {
if (this.state === SashState.AtMinimum) {
cursor = 'e-resize';
} else if (this.state === SashState.Maximum) {
} else if (this.state === SashState.AtMaximum) {
cursor = 'w-resize';
} else {
cursor = isMacintosh ? 'col-resize' : 'ew-resize';
Expand All @@ -406,7 +537,7 @@ export class Sash extends Disposable {
updateStyle();

if (!isMultisashResize) {
this.onDidEnablementChange(updateStyle, null, disposables);
this.onDidEnablementChange.event(updateStyle, null, disposables);
}

const onPointerMove = (e: PointerEvent) => {
Expand Down Expand Up @@ -472,10 +603,19 @@ export class Sash extends Disposable {
}
}

/**
* Forcefully stop any user interactions with this sash.
* Useful when hiding a parent component, while the user is still
* interacting with the sash.
*/
clearSashHoverState(): void {
Sash.onMouseLeave(this);
}

/**
* Layout the sash. The sash will size and position itself
* based on its provided {@link ISashLayoutProvider layout provider}.
*/
layout(): void {
if (this.orientation === Orientation.VERTICAL) {
const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
Expand Down
8 changes: 4 additions & 4 deletions src/vs/base/browser/ui/splitview/splitview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,16 +964,16 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible;

if (snappedBefore && collapsesUp[index] && (position > 0 || this.startSnappingEnabled)) {
sash.state = SashState.Minimum;
sash.state = SashState.AtMinimum;
} else if (snappedAfter && collapsesDown[index] && (position < this.contentSize || this.endSnappingEnabled)) {
sash.state = SashState.Maximum;
sash.state = SashState.AtMaximum;
} else {
sash.state = SashState.Disabled;
}
} else if (min && !max) {
sash.state = SashState.Minimum;
sash.state = SashState.AtMinimum;
} else if (!min && max) {
sash.state = SashState.Maximum;
sash.state = SashState.AtMaximum;
} else {
sash.state = SashState.Enabled;
}
Expand Down

0 comments on commit e7ea07d

Please sign in to comment.