提交 28438e23 编写于 作者: A Alex Dima

More cleanup in ScrollableElement

上级 a322bf08
......@@ -7,130 +7,51 @@
import * as Browser from 'vs/base/browser/browser';
import * as Platform from 'vs/base/common/platform';
import * as DomUtils from 'vs/base/browser/dom';
import {IMouseEvent, StandardMouseEvent} from 'vs/base/browser/mouseEvent';
import {IParent, Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {Disposable} from 'vs/base/common/lifecycle';
import {IMouseEvent, StandardMouseEvent, StandardMouseWheelEvent} from 'vs/base/browser/mouseEvent';
import {Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger} from 'vs/base/browser/globalMouseMoveMonitor';
import {Widget} from 'vs/base/browser/ui/widget';
import {TimeoutTimer} from 'vs/base/common/async';
import {FastDomNode, createFastDomNode} from 'vs/base/browser/styleMutator';
import {ScrollbarState} from 'vs/base/browser/ui/scrollbar/scrollbarState';
import {ScrollbarArrow, IMouseWheelEventFactory} from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import {ScrollbarArrow, ScrollbarArrowOptions} from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import {ScrollbarVisibilityController} from 'vs/base/browser/ui/scrollbar/scrollbarVisibilityController';
import {DelegateScrollable} from 'vs/base/common/scrollable';
/**
* The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
*/
const MOUSE_DRAG_RESET_DISTANCE = 140;
class VisibilityController extends Disposable {
private _visibility: Visibility;
private _visibleClassName: string;
private _invisibleClassName: string;
private _domNode: FastDomNode;
private _shouldBeVisible: boolean;
private _isNeeded: boolean;
private _isVisible: boolean;
private _revealTimer: TimeoutTimer;
constructor(visibility: Visibility, visibleClassName: string, invisibleClassName: string) {
super();
this._visibility = visibility;
this._visibleClassName = visibleClassName;
this._invisibleClassName = invisibleClassName;
this._domNode = null;
this._isVisible = false;
this._isNeeded = false;
this._shouldBeVisible = false;
this._revealTimer = this._register(new TimeoutTimer());
}
// ----------------- Hide / Reveal
private applyVisibilitySetting(shouldBeVisible: boolean): boolean {
if (this._visibility === Visibility.Hidden) {
return false;
}
if (this._visibility === Visibility.Visible) {
return true;
}
return shouldBeVisible;
}
public setShouldBeVisible(rawShouldBeVisible: boolean): void {
let shouldBeVisible = this.applyVisibilitySetting(rawShouldBeVisible);
if (this._shouldBeVisible !== shouldBeVisible) {
this._shouldBeVisible = shouldBeVisible;
this.ensureVisibility();
}
}
public setIsNeeded(isNeeded: boolean): void {
if (this._isNeeded !== isNeeded) {
this._isNeeded = isNeeded;
this.ensureVisibility();
}
}
public setDomNode(domNode: FastDomNode): void {
this._domNode = domNode;
this._domNode.setClassName(this._invisibleClassName);
// Now that the flags & the dom node are in a consistent state, ensure the Hidden/Visible configuration
this.setShouldBeVisible(false);
}
public ensureVisibility(): void {
if (!this._isNeeded) {
// Nothing to be rendered
this._hide(false);
return;
}
if (this._shouldBeVisible) {
this._reveal();
} else {
this._hide(true);
}
}
private _reveal(): void {
if (this._isVisible) {
return;
}
this._isVisible = true;
// The CSS animation doesn't play otherwise
this._revealTimer.setIfNotSet(() => {
this._domNode.setClassName(this._visibleClassName);
}, 0);
}
private _hide(withFadeAway: boolean): void {
this._revealTimer.cancel();
if (!this._isVisible) {
return;
}
this._isVisible = false;
this._domNode.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : ''));
}
}
export interface IMouseMoveEventData {
leftButton: boolean;
posx: number;
posy: number;
}
export interface ScrollbarHost {
onMouseWheel(mouseWheelEvent: StandardMouseWheelEvent): void;
onDragStart(): void;
onDragEnd(): void;
}
export interface AbstractScrollbarOptions {
forbidTranslate3dUse: boolean;
lazyRender:boolean;
host: ScrollbarHost;
scrollbarState: ScrollbarState;
visibility: Visibility;
extraScrollbarClassName: string;
scrollable: DelegateScrollable;
}
export abstract class AbstractScrollbar extends Widget {
protected _forbidTranslate3dUse: boolean;
protected _host: ScrollbarHost;
protected _scrollable: DelegateScrollable;
private _lazyRender: boolean;
private _parent: IParent;
private _scrollbarState: ScrollbarState;
private _visibilityController: VisibilityController;
private _visibilityController: ScrollbarVisibilityController;
private _mouseMoveMonitor: GlobalMouseMoveMonitor<IStandardMouseMoveEventData>;
public domNode: FastDomNode;
......@@ -138,23 +59,16 @@ export abstract class AbstractScrollbar extends Widget {
protected _shouldRender: boolean;
constructor(forbidTranslate3dUse: boolean, lazyRender:boolean, parent: IParent, scrollbarState: ScrollbarState, visibility: Visibility, extraScrollbarClassName: string) {
constructor(opts:AbstractScrollbarOptions) {
super();
this._forbidTranslate3dUse = forbidTranslate3dUse;
this._lazyRender = lazyRender;
this._parent = parent;
this._scrollbarState = scrollbarState;
this._visibilityController = this._register(new VisibilityController(visibility, 'visible scrollbar ' + extraScrollbarClassName, 'invisible scrollbar ' + extraScrollbarClassName));
this._forbidTranslate3dUse = opts.forbidTranslate3dUse;
this._lazyRender = opts.lazyRender;
this._host = opts.host;
this._scrollable = opts.scrollable;
this._scrollbarState = opts.scrollbarState;
this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName));
this._mouseMoveMonitor = this._register(new GlobalMouseMoveMonitor<IStandardMouseMoveEventData>());
this._shouldRender = true;
}
// ----------------- initialize & clean-up
/**
* Creates the container dom node for the scrollbar & hooks up the events
*/
protected _createDomNode(): void {
this.domNode = createFastDomNode(document.createElement('div'));
if (!this._forbidTranslate3dUse && Browser.canUseTranslate3d) {
// Put the scrollbar in its own layer
......@@ -167,11 +81,13 @@ export abstract class AbstractScrollbar extends Widget {
this.onmousedown(this.domNode.domNode, (e) => this._domNodeMouseDown(e));
}
// ----------------- creation
/**
* Creates the dom node for an arrow & adds it to the container
*/
protected _createArrow(className: string, top: number, left: number, bottom: number, right: number, bgWidth: number, bgHeight: number, mouseWheelEventFactory: IMouseWheelEventFactory): void {
let arrow = this._register(new ScrollbarArrow(className, top, left, bottom, right, bgWidth, bgHeight, mouseWheelEventFactory, this._parent));
protected _createArrow(opts:ScrollbarArrowOptions): void {
let arrow = this._register(new ScrollbarArrow(opts));
this.domNode.domNode.appendChild(arrow.bgDomNode);
this.domNode.domNode.appendChild(arrow.domNode);
}
......@@ -301,12 +217,12 @@ export abstract class AbstractScrollbar extends Widget {
},
() => {
this.slider.toggleClassName('active', false);
this._parent.onDragEnd();
this._host.onDragEnd();
}
);
e.preventDefault();
this._parent.onDragStart();
this._host.onDragStart();
}
}
......
......@@ -5,43 +5,61 @@
'use strict';
import * as Browser from 'vs/base/browser/browser';
import {AbstractScrollbar, IMouseMoveEventData} from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
import {AbstractScrollbar, ScrollbarHost, IMouseMoveEventData} from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
import {IMouseEvent, StandardMouseWheelEvent} from 'vs/base/browser/mouseEvent';
import {IDomNodePosition} from 'vs/base/browser/dom';
import {IParent, IScrollableElementOptions, Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollableElementResolvedOptions, Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {DelegateScrollable} from 'vs/base/common/scrollable';
import {ScrollbarState} from 'vs/base/browser/ui/scrollbar/scrollbarState';
import {ARROW_IMG_SIZE} from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
export class HorizontalScrollbar extends AbstractScrollbar {
private _scrollable: DelegateScrollable;
constructor(scrollable: DelegateScrollable, options: ScrollableElementResolvedOptions, host: ScrollbarHost) {
super({
forbidTranslate3dUse: options.forbidTranslate3dUse,
lazyRender: options.lazyRender,
host: host,
scrollbarState: new ScrollbarState(
(options.horizontalHasArrows ? options.arrowSize : 0),
(options.horizontal === Visibility.Hidden ? 0 : options.horizontalScrollbarSize),
(options.vertical === Visibility.Hidden ? 0 : options.verticalScrollbarSize)
),
visibility: options.horizontal,
extraScrollbarClassName: 'horizontal',
scrollable: scrollable
});
constructor(scrollable: DelegateScrollable, parent: IParent, options: IScrollableElementOptions) {
let s = new ScrollbarState(
(options.horizontalHasArrows ? options.arrowSize : 0),
(options.horizontal === Visibility.Hidden ? 0 : options.horizontalScrollbarSize),
(options.vertical === Visibility.Hidden ? 0 : options.verticalScrollbarSize)
);
super(options.forbidTranslate3dUse, options.lazyRender, parent, s, options.horizontal, 'horizontal');
this._scrollable = scrollable;
this._createDomNode();
if (options.horizontalHasArrows) {
let arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
let scrollbarDelta = (options.horizontalScrollbarSize - ARROW_IMG_SIZE) / 2;
this._createArrow('left-arrow', scrollbarDelta, arrowDelta, null, null, options.arrowSize, options.horizontalScrollbarSize, () => this._createMouseWheelEvent(1));
this._createArrow('right-arrow', scrollbarDelta, null, null, arrowDelta, options.arrowSize, options.horizontalScrollbarSize, () => this._createMouseWheelEvent(-1));
this._createArrow({
className: 'left-arrow',
top: scrollbarDelta,
left: arrowDelta,
bottom: void 0,
right: void 0,
bgWidth: options.arrowSize,
bgHeight: options.horizontalScrollbarSize,
onActivate: () => this._host.onMouseWheel(new StandardMouseWheelEvent(null, 1, 0)),
});
this._createArrow({
className: 'right-arrow',
top: scrollbarDelta,
left: void 0,
bottom: void 0,
right: arrowDelta,
bgWidth: options.arrowSize,
bgHeight: options.horizontalScrollbarSize,
onActivate: () => this._host.onMouseWheel(new StandardMouseWheelEvent(null, -1, 0)),
});
}
this._createSlider(Math.floor((options.horizontalScrollbarSize - options.horizontalSliderSize) / 2), 0, null, options.horizontalSliderSize);
}
protected _createMouseWheelEvent(sign: number) {
return new StandardMouseWheelEvent(null, sign, 0);
}
protected _updateSlider(sliderSize: number, sliderPosition: number): void {
this.slider.setWidth(sliderSize);
if (!this._forbidTranslate3dUse && Browser.canUseTranslate3d) {
......
......@@ -4,115 +4,119 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface IScrollableElementCreationOptions {
export interface ScrollableElementCreationOptions {
/**
* Prevent the scrollbar rendering from using translate3d. Defaults to false.
*/
forbidTranslate3dUse?: boolean;
/**
* The scrollable element should not do any DOM mutations until renderNow() is called.
* Defaults to false.
*/
lazyRender?: boolean;
/**
* CSS Class name for the scrollable element.
*/
className?: string;
/**
* Drop subtle horizontal and vertical shadows.
* Defaults to false.
*/
useShadows?: boolean;
/**
* Handle mouse wheel (listen to mouse wheel scrolling).
* Defaults to true
*/
handleMouseWheel?: boolean;
/**
* Flip axes. Treat vertical scrolling like horizontal and vice-versa.
* Defaults to false;
*/
flipAxes?: boolean;
/**
* A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.
* Defaults to 1.
*/
mouseWheelScrollSensitivity?: number;
/**
* Height for vertical arrows (top/bottom) and width for horizontal arrows (left/right).
* Defaults to 11.
*/
arrowSize?: number;
/**
* The dom node events should be bound to.
* If no listenOnDomNode is provided, the dom node passed to the constructor will be used for event listening.
*/
listenOnDomNode?: HTMLElement;
/**
* Control the visibility of the horizontal scrollbar.
* Accepted values: 'auto' (on mouse over), 'visible' (always visible), 'hidden' (never visible)
* Defaults to 'auto'.
*/
horizontal?: string;
/**
* Height (in px) of the horizontal scrollbar.
* Defaults to 10.
*/
horizontalScrollbarSize?: number;
/**
* Height (in px) of the horizontal scrollbar slider.
* Defaults to `horizontalScrollbarSize`
*/
horizontalSliderSize?: number;
/**
* Render arrows (left/right) for the horizontal scrollbar.
* Defaults to false.
*/
horizontalHasArrows?: boolean;
/**
* Control the visibility of the vertical scrollbar.
* Accepted values: 'auto' (on mouse over), 'visible' (always visible), 'hidden' (never visible)
* Defaults to 'auto'.
*/
vertical?: string;
/**
* Width (in px) of the vertical scrollbar.
* Defaults to 10.
*/
verticalScrollbarSize?: number;
/**
* Width (in px) of the vertical scrollbar slider.
* Defaults to `verticalScrollbarSize`
*/
verticalSliderSize?: number;
/**
* Render arrows (top/bottom) for the vertical scrollbar.
* Defaults to false.
*/
verticalHasArrows?: boolean;
/**
* Add a `last-scroll-time` attribute to scroll targets or parents of scroll targets matching the following class name
*/
saveLastScrollTimeOnClassName?: string;
}
export interface ScrollableElementResolvedOptions {
forbidTranslate3dUse: boolean;
lazyRender: boolean;
className: string;
useShadows: boolean;
handleMouseWheel: boolean;
flipAxes: boolean;
mouseWheelScrollSensitivity: number;
arrowSize: number;
listenOnDomNode: HTMLElement;
horizontal: Visibility;
horizontalScrollbarSize: number;
horizontalSliderSize: number;
horizontalHasArrows: boolean;
vertical: Visibility;
verticalScrollbarSize: number;
verticalSliderSize: number;
verticalHasArrows: boolean;
saveLastScrollTimeOnClassName: string;
}
export interface IOverviewRulerLayoutInfo {
parent: HTMLElement;
insertBefore: HTMLElement;
......@@ -128,14 +132,10 @@ export interface IDimensions {
*/
export interface IScrollableElement {
/**
* Get the generated 'scrollable' dom node
*/
getDomNode(): HTMLElement;
/**
* Let the scrollable element know that the generated dom node's width / height might have changed.
*/
onElementDimensions(dimensions?: IDimensions): void;
/**
......@@ -143,46 +143,19 @@ export interface IScrollableElement {
*/
dispose(): void;
/**
* Render / mutate the DOM now.
* Should be used together with the ctor option `lazyRender`.
*/
renderNow(): void;
/**
* Update the class name of the scrollable element.
*/
updateClassName(newClassName: string): void;
/**
* Update configuration options for the scrollbar.
* Really this is Editor.IEditorScrollbarOptions, but base shouldn't
* depend on Editor.
*/
updateOptions(newOptions: IScrollableElementCreationOptions): void;
updateClassName(newClassName: string): void;
getOverviewRulerLayoutInfo(): IOverviewRulerLayoutInfo;
/**
* Delegate a mouse down event to the vertical scrollbar.
* This is to help with clicking somewhere else and having the scrollbar react.
*/
delegateVerticalScrollbarMouseDown(browserEvent: MouseEvent): void;
updateOptions(newOptions: ScrollableElementCreationOptions): void;
}
getOverviewRulerLayoutInfo(): IOverviewRulerLayoutInfo;
export interface IMouseWheelEvent {
browserEvent: MouseWheelEvent;
deltaX: number;
deltaY: number;
preventDefault(): void;
stopPropagation(): void;
}
export interface IParent {
onMouseWheel(mouseWheelEvent: IMouseWheelEvent): void;
onDragStart(): void;
onDragEnd(): void;
delegateVerticalScrollbarMouseDown(browserEvent: MouseEvent): void;
}
export enum Visibility {
......@@ -202,23 +175,3 @@ export function visibilityFromString(visibility: string): Visibility {
}
}
export interface IScrollableElementOptions {
forbidTranslate3dUse: boolean;
lazyRender: boolean;
className: string;
useShadows: boolean;
handleMouseWheel: boolean;
flipAxes: boolean;
mouseWheelScrollSensitivity: number;
arrowSize: number;
listenOnDomNode: HTMLElement;
horizontal: Visibility;
horizontalScrollbarSize: number;
horizontalSliderSize: number;
horizontalHasArrows: boolean;
vertical: Visibility;
verticalScrollbarSize: number;
verticalSliderSize: number;
verticalHasArrows: boolean;
saveLastScrollTimeOnClassName: string;
}
......@@ -12,21 +12,22 @@ import {StandardMouseWheelEvent, IMouseEvent} from 'vs/base/browser/mouseEvent';
import {HorizontalScrollbar} from 'vs/base/browser/ui/scrollbar/horizontalScrollbar';
import {VerticalScrollbar} from 'vs/base/browser/ui/scrollbar/verticalScrollbar';
import {
IScrollableElementOptions, IDimensions, IMouseWheelEvent, visibilityFromString,
IScrollableElement, IScrollableElementCreationOptions, IOverviewRulerLayoutInfo
ScrollableElementResolvedOptions, IDimensions, visibilityFromString,
IScrollableElement, ScrollableElementCreationOptions, IOverviewRulerLayoutInfo
} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {IScrollable, DelegateScrollable} from 'vs/base/common/scrollable';
import {Widget} from 'vs/base/browser/ui/widget';
import {TimeoutTimer} from 'vs/base/common/async';
import {FastDomNode, createFastDomNode} from 'vs/base/browser/styleMutator';
import {ScrollbarHost} from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
const HIDE_TIMEOUT = 500;
const SCROLL_WHEEL_SENSITIVITY = 50;
export class ScrollableElement extends Widget implements IScrollableElement {
private _options: IScrollableElementOptions;
private _options: ScrollableElementResolvedOptions;
private _scrollable: DelegateScrollable;
private _verticalScrollbar: VerticalScrollbar;
private _horizontalScrollbar: HorizontalScrollbar;
......@@ -47,15 +48,20 @@ export class ScrollableElement extends Widget implements IScrollableElement {
private _hideTimeout: TimeoutTimer;
private _shouldRender: boolean;
constructor(element: HTMLElement, scrollable:IScrollable, options: IScrollableElementCreationOptions, dimensions: IDimensions = null) {
constructor(element: HTMLElement, scrollable:IScrollable, options: ScrollableElementCreationOptions, dimensions: IDimensions = null) {
super();
element.style.overflow = 'hidden';
this._options = this._createOptions(options);
this._options = resolveOptions(options);
this._scrollable = this._register(new DelegateScrollable(scrollable, () => this._onScroll()));
this._verticalScrollbar = this._register(new VerticalScrollbar(this._scrollable, this, this._options));
this._horizontalScrollbar = this._register(new HorizontalScrollbar(this._scrollable, this, this._options));
let scrollbarHost:ScrollbarHost = {
onMouseWheel: (mouseWheelEvent: StandardMouseWheelEvent) => this._onMouseWheel(mouseWheelEvent),
onDragStart: () => this._onDragStart(),
onDragEnd: () => this._onDragEnd(),
};
this._verticalScrollbar = this._register(new VerticalScrollbar(this._scrollable, this._options, scrollbarHost));
this._horizontalScrollbar = this._register(new HorizontalScrollbar(this._scrollable, this._options, scrollbarHost));
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-scrollable-element ' + this._options.className;
......@@ -105,6 +111,9 @@ export class ScrollableElement extends Widget implements IScrollableElement {
super.dispose();
}
/**
* Get the generated 'scrollable' dom node
*/
public getDomNode(): HTMLElement {
return this._domNode;
}
......@@ -116,10 +125,17 @@ export class ScrollableElement extends Widget implements IScrollableElement {
};
}
/**
* Delegate a mouse down event to the vertical scrollbar.
* This is to help with clicking somewhere else and having the scrollbar react.
*/
public delegateVerticalScrollbarMouseDown(browserEvent: MouseEvent): void {
this._verticalScrollbar.delegateMouseDown(browserEvent);
}
/**
* Let the scrollable element know that the generated dom node's width / height might have changed.
*/
public onElementDimensions(dimensions: IDimensions = null, synchronous: boolean = false): void {
if (synchronous) {
this._actualElementDimensions(dimensions);
......@@ -142,6 +158,9 @@ export class ScrollableElement extends Widget implements IScrollableElement {
this._shouldRender = this._horizontalScrollbar.onElementSize(width) || this._shouldRender;
}
/**
* Update the class name of the scrollable element.
*/
public updateClassName(newClassName: string): void {
this._options.className = newClassName;
// Defaults are different on Macs
......@@ -151,9 +170,14 @@ export class ScrollableElement extends Widget implements IScrollableElement {
this._domNode.className = 'monaco-scrollable-element ' + this._options.className;
}
public updateOptions(newOptions: IScrollableElementCreationOptions): void {
/**
* Update configuration options for the scrollbar.
* Really this is Editor.IEditorScrollbarOptions, but base shouldn't
* depend on Editor.
*/
public updateOptions(newOptions: ScrollableElementCreationOptions): void {
// only support handleMouseWheel changes for now
let massagedOptions = this._createOptions(newOptions);
let massagedOptions = resolveOptions(newOptions);
this._options.handleMouseWheel = massagedOptions.handleMouseWheel;
this._options.mouseWheelScrollSensitivity = massagedOptions.mouseWheelScrollSensitivity;
this._setListeningToMouseWheel(this._options.handleMouseWheel);
......@@ -176,7 +200,7 @@ export class ScrollableElement extends Widget implements IScrollableElement {
if (shouldListen) {
let onMouseWheel = (browserEvent: MouseWheelEvent) => {
let e = new StandardMouseWheelEvent(browserEvent);
this.onMouseWheel(e);
this._onMouseWheel(e);
};
this._mouseWheelToDispose.push(DomUtils.addDisposableListener(this._listenOnDomNode, 'mousewheel', onMouseWheel));
......@@ -184,7 +208,7 @@ export class ScrollableElement extends Widget implements IScrollableElement {
}
}
public onMouseWheel(e: IMouseWheelEvent): void {
private _onMouseWheel(e: StandardMouseWheelEvent): void {
if (Platform.isMacintosh && e.browserEvent && this._options.saveLastScrollTimeOnClassName) {
// Mark dom node with timestamp of wheel event
let target = <HTMLElement>e.browserEvent.target;
......@@ -271,6 +295,10 @@ export class ScrollableElement extends Widget implements IScrollableElement {
}
}
/**
* Render / mutate the DOM now.
* Should be used together with the ctor option `lazyRender`.
*/
public renderNow(): void {
if (!this._options.lazyRender) {
throw new Error('Please use `lazyRender` together with `renderNow`!');
......@@ -301,12 +329,12 @@ export class ScrollableElement extends Widget implements IScrollableElement {
// -------------------- fade in / fade out --------------------
public onDragStart(): void {
private _onDragStart(): void {
this._isDragging = true;
this._reveal();
}
public onDragEnd(): void {
private _onDragEnd(): void {
this._isDragging = false;
this._hide();
}
......@@ -337,51 +365,41 @@ export class ScrollableElement extends Widget implements IScrollableElement {
private _scheduleHide(): void {
this._hideTimeout.cancelAndSet(() => this._hide(), HIDE_TIMEOUT);
}
}
// -------------------- size & layout --------------------
private _createOptions(options: IScrollableElementCreationOptions): IScrollableElementOptions {
function ensureValue<V>(source: any, prop: string, value: V) {
if (source.hasOwnProperty(prop)) {
return <V>source[prop];
}
return value;
}
let result: IScrollableElementOptions = {
forbidTranslate3dUse: ensureValue(options, 'forbidTranslate3dUse', false),
lazyRender: ensureValue(options, 'lazyRender', false),
className: ensureValue(options, 'className', ''),
useShadows: ensureValue(options, 'useShadows', true),
handleMouseWheel: ensureValue(options, 'handleMouseWheel', true),
flipAxes: ensureValue(options, 'flipAxes', false),
mouseWheelScrollSensitivity: ensureValue(options, 'mouseWheelScrollSensitivity', 1),
arrowSize: ensureValue(options, 'arrowSize', 11),
listenOnDomNode: ensureValue<HTMLElement>(options, 'listenOnDomNode', null),
horizontal: visibilityFromString(ensureValue(options, 'horizontal', 'auto')),
horizontalScrollbarSize: ensureValue(options, 'horizontalScrollbarSize', 10),
horizontalSliderSize: 0,
horizontalHasArrows: ensureValue(options, 'horizontalHasArrows', false),
vertical: visibilityFromString(ensureValue(options, 'vertical', 'auto')),
verticalScrollbarSize: ensureValue(options, 'verticalScrollbarSize', 10),
verticalHasArrows: ensureValue(options, 'verticalHasArrows', false),
verticalSliderSize: 0,
saveLastScrollTimeOnClassName: ensureValue(options, 'saveLastScrollTimeOnClassName', null)
};
result.horizontalSliderSize = ensureValue(options, 'horizontalSliderSize', result.horizontalScrollbarSize);
result.verticalSliderSize = ensureValue(options, 'verticalSliderSize', result.verticalScrollbarSize);
// Defaults are different on Macs
if (Platform.isMacintosh) {
result.className += ' mac';
}
return result;
function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableElementResolvedOptions {
let result: ScrollableElementResolvedOptions = {
forbidTranslate3dUse: (typeof opts.forbidTranslate3dUse !== 'undefined' ? opts.forbidTranslate3dUse : false),
lazyRender: (typeof opts.lazyRender !== 'undefined' ? opts.lazyRender : false),
className: (typeof opts.className !== 'undefined' ? opts.className : ''),
useShadows: (typeof opts.useShadows !== 'undefined' ? opts.useShadows : true),
handleMouseWheel: (typeof opts.handleMouseWheel !== 'undefined' ? opts.handleMouseWheel : true),
flipAxes: (typeof opts.flipAxes !== 'undefined' ? opts.flipAxes : false),
mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1),
arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11),
listenOnDomNode: (typeof opts.listenOnDomNode !== 'undefined' ? opts.listenOnDomNode : null),
horizontal: visibilityFromString(typeof opts.horizontal !== 'undefined' ? opts.horizontal : 'auto'),
horizontalScrollbarSize: (typeof opts.horizontalScrollbarSize !== 'undefined' ? opts.horizontalScrollbarSize : 10),
horizontalSliderSize: (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : 0),
horizontalHasArrows: (typeof opts.horizontalHasArrows !== 'undefined' ? opts.horizontalHasArrows : false),
vertical: visibilityFromString(typeof opts.vertical !== 'undefined' ? opts.vertical : 'auto'),
verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10),
verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false),
verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0),
saveLastScrollTimeOnClassName: (typeof opts.saveLastScrollTimeOnClassName !== 'undefined' ? opts.saveLastScrollTimeOnClassName : null)
};
result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize);
result.verticalSliderSize = (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : result.verticalScrollbarSize);
// Defaults are different on Macs
if (Platform.isMacintosh) {
result.className += ' mac';
}
return result;
}
......@@ -5,47 +5,76 @@
'use strict';
import {IMouseEvent} from 'vs/base/browser/mouseEvent';
import {IMouseWheelEvent, IParent} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger} from 'vs/base/browser/globalMouseMoveMonitor';
import {Widget} from 'vs/base/browser/ui/widget';
import {TimeoutTimer, IntervalTimer} from 'vs/base/common/async';
export interface IMouseWheelEventFactory {
(): IMouseWheelEvent;
}
/**
* The arrow image size.
*/
export const ARROW_IMG_SIZE = 11;
export interface ScrollbarArrowOptions {
onActivate: () => void;
className: string;
bgWidth: number;
bgHeight: number;
top?: number;
left?: number;
bottom?: number;
right?: number;
}
export class ScrollbarArrow extends Widget {
private _parent: IParent;
private _mouseWheelEventFactory: IMouseWheelEventFactory;
private _onActivate: () => void;
public bgDomNode: HTMLElement;
public domNode: HTMLElement;
private _mousedownRepeatTimer: IntervalTimer;
private _mousedownScheduleRepeatTimer: TimeoutTimer;
private _mouseMoveMonitor: GlobalMouseMoveMonitor<IStandardMouseMoveEventData>;
constructor(className: string, top: number, left: number, bottom: number, right: number, bgWidth: number, bgHeight: number, mouseWheelEventFactory: IMouseWheelEventFactory, parent: IParent) {
constructor(opts:ScrollbarArrowOptions) {
super();
this._parent = parent;
this._mouseWheelEventFactory = mouseWheelEventFactory;
this._onActivate = opts.onActivate;
this.bgDomNode = document.createElement('div');
this.bgDomNode.className = 'arrow-background';
this.bgDomNode.style.position = 'absolute';
setSize(this.bgDomNode, bgWidth, bgHeight);
setPosition(this.bgDomNode, (top !== null ? 0 : null), (left !== null ? 0 : null), (bottom !== null ? 0 : null), (right !== null ? 0 : null));
this.bgDomNode.style.width = opts.bgWidth + 'px';
this.bgDomNode.style.height = opts.bgHeight + 'px';
if (typeof opts.top !== 'undefined') {
this.bgDomNode.style.top = '0px';
}
if (typeof opts.left !== 'undefined') {
this.bgDomNode.style.left = '0px';
}
if (typeof opts.bottom !== 'undefined') {
this.bgDomNode.style.bottom = '0px';
}
if (typeof opts.right !== 'undefined') {
this.bgDomNode.style.right = '0px';
}
this.domNode = document.createElement('div');
this.domNode.className = className;
this.domNode.className = opts.className;
this.domNode.style.position = 'absolute';
setSize(this.domNode, ARROW_IMG_SIZE, ARROW_IMG_SIZE);
setPosition(this.domNode, top, left, bottom, right);
this.domNode.style.width = ARROW_IMG_SIZE + 'px';
this.domNode.style.height = ARROW_IMG_SIZE + 'px';
if (typeof opts.top !== 'undefined') {
this.domNode.style.top = opts.top + 'px';
}
if (typeof opts.left !== 'undefined') {
this.domNode.style.left = opts.left + 'px';
}
if (typeof opts.bottom !== 'undefined') {
this.domNode.style.bottom = opts.bottom + 'px';
}
if (typeof opts.right !== 'undefined') {
this.domNode.style.right = opts.right + 'px';
}
this._mouseMoveMonitor = this._register(new GlobalMouseMoveMonitor<IStandardMouseMoveEventData>());
this.onmousedown(this.bgDomNode, (e) => this._arrowMouseDown(e));
......@@ -56,15 +85,11 @@ export class ScrollbarArrow extends Widget {
}
private _arrowMouseDown(e: IMouseEvent): void {
let repeater = () => {
this._parent.onMouseWheel(this._mouseWheelEventFactory());
};
let scheduleRepeater = () => {
this._mousedownRepeatTimer.cancelAndSet(repeater, 1000 / 24);
this._mousedownRepeatTimer.cancelAndSet(() => this._onActivate(), 1000 / 24);
};
repeater();
this._onActivate();
this._mousedownRepeatTimer.cancel();
this._mousedownScheduleRepeatTimer.cancelAndSet(scheduleRepeater, 200);
......@@ -82,27 +107,3 @@ export class ScrollbarArrow extends Widget {
e.preventDefault();
}
}
function setPosition(domNode: HTMLElement, top: number, left: number, bottom: number, right: number) {
if (top !== null) {
domNode.style.top = top + 'px';
}
if (left !== null) {
domNode.style.left = left + 'px';
}
if (bottom !== null) {
domNode.style.bottom = bottom + 'px';
}
if (right !== null) {
domNode.style.right = right + 'px';
}
}
function setSize(domNode: HTMLElement, width: number, height: number) {
if (width !== null) {
domNode.style.width = width + 'px';
}
if (height !== null) {
domNode.style.height = height + 'px';
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {Disposable} from 'vs/base/common/lifecycle';
import {TimeoutTimer} from 'vs/base/common/async';
import {FastDomNode} from 'vs/base/browser/styleMutator';
export class ScrollbarVisibilityController extends Disposable {
private _visibility: Visibility;
private _visibleClassName: string;
private _invisibleClassName: string;
private _domNode: FastDomNode;
private _shouldBeVisible: boolean;
private _isNeeded: boolean;
private _isVisible: boolean;
private _revealTimer: TimeoutTimer;
constructor(visibility: Visibility, visibleClassName: string, invisibleClassName: string) {
super();
this._visibility = visibility;
this._visibleClassName = visibleClassName;
this._invisibleClassName = invisibleClassName;
this._domNode = null;
this._isVisible = false;
this._isNeeded = false;
this._shouldBeVisible = false;
this._revealTimer = this._register(new TimeoutTimer());
}
// ----------------- Hide / Reveal
private applyVisibilitySetting(shouldBeVisible: boolean): boolean {
if (this._visibility === Visibility.Hidden) {
return false;
}
if (this._visibility === Visibility.Visible) {
return true;
}
return shouldBeVisible;
}
public setShouldBeVisible(rawShouldBeVisible: boolean): void {
let shouldBeVisible = this.applyVisibilitySetting(rawShouldBeVisible);
if (this._shouldBeVisible !== shouldBeVisible) {
this._shouldBeVisible = shouldBeVisible;
this.ensureVisibility();
}
}
public setIsNeeded(isNeeded: boolean): void {
if (this._isNeeded !== isNeeded) {
this._isNeeded = isNeeded;
this.ensureVisibility();
}
}
public setDomNode(domNode: FastDomNode): void {
this._domNode = domNode;
this._domNode.setClassName(this._invisibleClassName);
// Now that the flags & the dom node are in a consistent state, ensure the Hidden/Visible configuration
this.setShouldBeVisible(false);
}
public ensureVisibility(): void {
if (!this._isNeeded) {
// Nothing to be rendered
this._hide(false);
return;
}
if (this._shouldBeVisible) {
this._reveal();
} else {
this._hide(true);
}
}
private _reveal(): void {
if (this._isVisible) {
return;
}
this._isVisible = true;
// The CSS animation doesn't play otherwise
this._revealTimer.setIfNotSet(() => {
this._domNode.setClassName(this._visibleClassName);
}, 0);
}
private _hide(withFadeAway: boolean): void {
this._revealTimer.cancel();
if (!this._isVisible) {
return;
}
this._isVisible = false;
this._domNode.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : ''));
}
}
\ No newline at end of file
......@@ -5,44 +5,62 @@
'use strict';
import * as Browser from 'vs/base/browser/browser';
import {AbstractScrollbar, IMouseMoveEventData} from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
import {AbstractScrollbar, ScrollbarHost, IMouseMoveEventData} from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
import {IMouseEvent, StandardMouseWheelEvent} from 'vs/base/browser/mouseEvent';
import {IDomNodePosition} from 'vs/base/browser/dom';
import {IParent, IScrollableElementOptions, Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollableElementResolvedOptions, Visibility} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {DelegateScrollable} from 'vs/base/common/scrollable';
import {ScrollbarState} from 'vs/base/browser/ui/scrollbar/scrollbarState';
import {ARROW_IMG_SIZE} from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
export class VerticalScrollbar extends AbstractScrollbar {
private _scrollable: DelegateScrollable;
constructor(scrollable: DelegateScrollable, options: ScrollableElementResolvedOptions, host: ScrollbarHost) {
super({
forbidTranslate3dUse: options.forbidTranslate3dUse,
lazyRender: options.lazyRender,
host: host,
scrollbarState: new ScrollbarState(
(options.verticalHasArrows ? options.arrowSize : 0),
(options.vertical === Visibility.Hidden ? 0 : options.verticalScrollbarSize),
// give priority to vertical scroll bar over horizontal and let it scroll all the way to the bottom
0
),
visibility: options.vertical,
extraScrollbarClassName: 'vertical',
scrollable: scrollable
});
constructor(scrollable: DelegateScrollable, parent: IParent, options: IScrollableElementOptions) {
let s = new ScrollbarState(
(options.verticalHasArrows ? options.arrowSize : 0),
(options.vertical === Visibility.Hidden ? 0 : options.verticalScrollbarSize),
// give priority to vertical scroll bar over horizontal and let it scroll all the way to the bottom
0
);
super(options.forbidTranslate3dUse, options.lazyRender, parent, s, options.vertical, 'vertical');
this._scrollable = scrollable;
this._createDomNode();
if (options.verticalHasArrows) {
let arrowDelta = (options.arrowSize - ARROW_IMG_SIZE) / 2;
let scrollbarDelta = (options.verticalScrollbarSize - ARROW_IMG_SIZE) / 2;
this._createArrow('up-arrow', arrowDelta, scrollbarDelta, null, null, options.verticalScrollbarSize, options.arrowSize, () => this._createMouseWheelEvent(1));
this._createArrow('down-arrow', null, scrollbarDelta, arrowDelta, null, options.verticalScrollbarSize, options.arrowSize, () => this._createMouseWheelEvent(-1));
this._createArrow({
className: 'up-arrow',
top: arrowDelta,
left: scrollbarDelta,
bottom: void 0,
right: void 0,
bgWidth: options.verticalScrollbarSize,
bgHeight: options.arrowSize,
onActivate: () => this._host.onMouseWheel(new StandardMouseWheelEvent(null, 0, 1)),
});
this._createArrow({
className: 'down-arrow',
top: void 0,
left: scrollbarDelta,
bottom: arrowDelta,
right: void 0,
bgWidth: options.verticalScrollbarSize,
bgHeight: options.arrowSize,
onActivate: () => this._host.onMouseWheel(new StandardMouseWheelEvent(null, 0, -1)),
});
}
this._createSlider(0, Math.floor((options.verticalScrollbarSize - options.verticalSliderSize) / 2), options.verticalSliderSize, null);
}
protected _createMouseWheelEvent(sign: number) {
return new StandardMouseWheelEvent(null, 0, sign);
}
protected _updateSlider(sliderSize: number, sliderPosition: number): void {
this.slider.setHeight(sliderSize);
if (!this._forbidTranslate3dUse && Browser.canUseTranslate3d) {
......
......@@ -6,7 +6,7 @@
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import {IOverviewRulerLayoutInfo, IScrollableElement, IScrollableElementCreationOptions} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {IOverviewRulerLayoutInfo, IScrollableElement, ScrollableElementCreationOptions} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElementImpl';
import {EventType, IConfiguration, IConfigurationChangedEvent, IScrollEvent, IViewEventBus} from 'vs/editor/common/editorCommon';
import {EditorScrollable} from 'vs/editor/common/viewLayout/editorScrollable';
......@@ -41,7 +41,7 @@ export class ScrollManager implements IDisposable {
var configScrollbarOpts = this.configuration.editor.scrollbar;
var scrollbarOptions:IScrollableElementCreationOptions = {
var scrollbarOptions:ScrollableElementCreationOptions = {
listenOnDomNode: viewDomNode,
vertical: configScrollbarOpts.vertical,
horizontal: configScrollbarOpts.horizontal,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册