123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as dom from '../../base/browser/dom.js';
- import { GlobalMouseMoveMonitor } from '../../base/browser/globalMouseMoveMonitor.js';
- import { StandardMouseEvent } from '../../base/browser/mouseEvent.js';
- import { RunOnceScheduler } from '../../base/common/async.js';
- import { Disposable } from '../../base/common/lifecycle.js';
- import { asCssVariableName } from '../../platform/theme/common/colorRegistry.js';
- /**
- * Coordinates relative to the whole document (e.g. mouse event's pageX and pageY)
- */
- export class PageCoordinates {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- this._pageCoordinatesBrand = undefined;
- }
- toClientCoordinates() {
- return new ClientCoordinates(this.x - dom.StandardWindow.scrollX, this.y - dom.StandardWindow.scrollY);
- }
- }
- /**
- * Coordinates within the application's client area (i.e. origin is document's scroll position).
- *
- * For example, clicking in the top-left corner of the client area will
- * always result in a mouse event with a client.x value of 0, regardless
- * of whether the page is scrolled horizontally.
- */
- export class ClientCoordinates {
- constructor(clientX, clientY) {
- this.clientX = clientX;
- this.clientY = clientY;
- this._clientCoordinatesBrand = undefined;
- }
- toPageCoordinates() {
- return new PageCoordinates(this.clientX + dom.StandardWindow.scrollX, this.clientY + dom.StandardWindow.scrollY);
- }
- }
- /**
- * The position of the editor in the page.
- */
- export class EditorPagePosition {
- constructor(x, y, width, height) {
- this.x = x;
- this.y = y;
- this.width = width;
- this.height = height;
- this._editorPagePositionBrand = undefined;
- }
- }
- export function createEditorPagePosition(editorViewDomNode) {
- const editorPos = dom.getDomNodePagePosition(editorViewDomNode);
- return new EditorPagePosition(editorPos.left, editorPos.top, editorPos.width, editorPos.height);
- }
- export class EditorMouseEvent extends StandardMouseEvent {
- constructor(e, editorViewDomNode) {
- super(e);
- this._editorMouseEventBrand = undefined;
- this.pos = new PageCoordinates(this.posx, this.posy);
- this.editorPos = createEditorPagePosition(editorViewDomNode);
- }
- }
- export class EditorMouseEventFactory {
- constructor(editorViewDomNode) {
- this._editorViewDomNode = editorViewDomNode;
- }
- _create(e) {
- return new EditorMouseEvent(e, this._editorViewDomNode);
- }
- onContextMenu(target, callback) {
- return dom.addDisposableListener(target, 'contextmenu', (e) => {
- callback(this._create(e));
- });
- }
- onMouseUp(target, callback) {
- return dom.addDisposableListener(target, 'mouseup', (e) => {
- callback(this._create(e));
- });
- }
- onMouseDown(target, callback) {
- return dom.addDisposableListener(target, 'mousedown', (e) => {
- callback(this._create(e));
- });
- }
- onMouseLeave(target, callback) {
- return dom.addDisposableNonBubblingMouseOutListener(target, (e) => {
- callback(this._create(e));
- });
- }
- onMouseMoveThrottled(target, callback, merger, minimumTimeMs) {
- const myMerger = (lastEvent, currentEvent) => {
- return merger(lastEvent, this._create(currentEvent));
- };
- return dom.addDisposableThrottledListener(target, 'mousemove', callback, myMerger, minimumTimeMs);
- }
- }
- export class EditorPointerEventFactory {
- constructor(editorViewDomNode) {
- this._editorViewDomNode = editorViewDomNode;
- }
- _create(e) {
- return new EditorMouseEvent(e, this._editorViewDomNode);
- }
- onPointerUp(target, callback) {
- return dom.addDisposableListener(target, 'pointerup', (e) => {
- callback(this._create(e));
- });
- }
- onPointerDown(target, callback) {
- return dom.addDisposableListener(target, 'pointerdown', (e) => {
- callback(this._create(e));
- });
- }
- onPointerLeave(target, callback) {
- return dom.addDisposableNonBubblingPointerOutListener(target, (e) => {
- callback(this._create(e));
- });
- }
- onPointerMoveThrottled(target, callback, merger, minimumTimeMs) {
- const myMerger = (lastEvent, currentEvent) => {
- return merger(lastEvent, this._create(currentEvent));
- };
- return dom.addDisposableThrottledListener(target, 'pointermove', callback, myMerger, minimumTimeMs);
- }
- }
- export class GlobalEditorMouseMoveMonitor extends Disposable {
- constructor(editorViewDomNode) {
- super();
- this._editorViewDomNode = editorViewDomNode;
- this._globalMouseMoveMonitor = this._register(new GlobalMouseMoveMonitor());
- this._keydownListener = null;
- }
- startMonitoring(initialElement, initialButtons, merger, mouseMoveCallback, onStopCallback) {
- // Add a <<capture>> keydown event listener that will cancel the monitoring
- // if something other than a modifier key is pressed
- this._keydownListener = dom.addStandardDisposableListener(document, 'keydown', (e) => {
- const kb = e.toKeybinding();
- if (kb.isModifierKey()) {
- // Allow modifier keys
- return;
- }
- this._globalMouseMoveMonitor.stopMonitoring(true, e.browserEvent);
- }, true);
- const myMerger = (lastEvent, currentEvent) => {
- return merger(lastEvent, new EditorMouseEvent(currentEvent, this._editorViewDomNode));
- };
- this._globalMouseMoveMonitor.startMonitoring(initialElement, initialButtons, myMerger, mouseMoveCallback, (e) => {
- this._keydownListener.dispose();
- onStopCallback(e);
- });
- }
- stopMonitoring() {
- this._globalMouseMoveMonitor.stopMonitoring(true);
- }
- }
- /**
- * A helper to create dynamic css rules, bound to a class name.
- * Rules are reused.
- * Reference counting and delayed garbage collection ensure that no rules leak.
- */
- export class DynamicCssRules {
- constructor(_editor) {
- this._editor = _editor;
- this._counter = 0;
- this._rules = new Map();
- // We delay garbage collection so that hanging rules can be reused.
- this._garbageCollectionScheduler = new RunOnceScheduler(() => this.garbageCollect(), 1000);
- }
- createClassNameRef(options) {
- const rule = this.getOrCreateRule(options);
- rule.increaseRefCount();
- return {
- className: rule.className,
- dispose: () => {
- rule.decreaseRefCount();
- this._garbageCollectionScheduler.schedule();
- }
- };
- }
- getOrCreateRule(properties) {
- const key = this.computeUniqueKey(properties);
- let existingRule = this._rules.get(key);
- if (!existingRule) {
- const counter = this._counter++;
- existingRule = new RefCountedCssRule(key, `dyn-rule-${counter}`, dom.isInShadowDOM(this._editor.getContainerDomNode())
- ? this._editor.getContainerDomNode()
- : undefined, properties);
- this._rules.set(key, existingRule);
- }
- return existingRule;
- }
- computeUniqueKey(properties) {
- return JSON.stringify(properties);
- }
- garbageCollect() {
- for (const rule of this._rules.values()) {
- if (!rule.hasReferences()) {
- this._rules.delete(rule.key);
- rule.dispose();
- }
- }
- }
- }
- class RefCountedCssRule {
- constructor(key, className, _containerElement, properties) {
- this.key = key;
- this.className = className;
- this.properties = properties;
- this._referenceCount = 0;
- this._styleElement = dom.createStyleSheet(_containerElement);
- this._styleElement.textContent = this.getCssText(this.className, this.properties);
- }
- getCssText(className, properties) {
- let str = `.${className} {`;
- for (const prop in properties) {
- const value = properties[prop];
- let cssValue;
- if (typeof value === 'object') {
- cssValue = `var(${asCssVariableName(value.id)})`;
- }
- else {
- cssValue = value;
- }
- const cssPropName = camelToDashes(prop);
- str += `\n\t${cssPropName}: ${cssValue};`;
- }
- str += `\n}`;
- return str;
- }
- dispose() {
- this._styleElement.remove();
- }
- increaseRefCount() {
- this._referenceCount++;
- }
- decreaseRefCount() {
- this._referenceCount--;
- }
- hasReferences() {
- return this._referenceCount > 0;
- }
- }
- function camelToDashes(str) {
- return str.replace(/(^[A-Z])/, ([first]) => first.toLowerCase())
- .replace(/([A-Z])/g, ([letter]) => `-${letter.toLowerCase()}`);
- }
|