From 9b4f91750d61579a24e2e156a40f5e15698db698 Mon Sep 17 00:00:00 2001 From: lumix Date: Fri, 1 Aug 2025 19:24:41 +0800 Subject: [PATCH] refactor: remove unused disabled event handlers in scrollbar and selection (#4957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 白熱 --- examples/src/sheets/main.ts | 49 +- packages/engine-render/src/base-object.ts | 1 + .../engine-render/src/scene.input-manager.ts | 10 + packages/engine-render/src/scene.ts | 18 +- .../engine-render/src/shape/scroll-bar.ts | 756 ++++++++++-------- packages/engine-render/src/shape/shape.ts | 3 +- .../ref-selections.render-service.ts | 2 +- .../base-selection-render.service.ts | 3 + .../selection/selection-render.service.ts | 4 +- .../selection/selection-shape-extension.ts | 6 +- 10 files changed, 455 insertions(+), 397 deletions(-) diff --git a/examples/src/sheets/main.ts b/examples/src/sheets/main.ts index b112b42c21..e732762c7f 100644 --- a/examples/src/sheets/main.ts +++ b/examples/src/sheets/main.ts @@ -21,6 +21,7 @@ import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { DEFAULT_WORKBOOK_DATA_DEMO } from '@univerjs/mockdata'; import zhCN from '@univerjs/mockdata/locales/zh-CN'; import { UniverNetworkPlugin } from '@univerjs/network'; import { UniverRPCMainThreadPlugin } from '@univerjs/rpc'; @@ -143,53 +144,7 @@ function createNewInstance() { // create univer sheet instance if (!IS_E2E) { - univer.createUnit(UniverInstanceType.UNIVER_SHEET, { - id: 'fbpG8l', - sheetOrder: [ - 'xeyZKmZIRXwhSzwbec9-c', - ], - sheets: { - 'xeyZKmZIRXwhSzwbec9-c': { - id: 'xeyZKmZIRXwhSzwbec9-c', - name: 'Sheet1', - tabColor: '', - hidden: 0, - rowCount: 1000, - columnCount: 20, - zoomRatio: 1, - freeze: { - xSplit: 0, - ySplit: 0, - startRow: -1, - startColumn: -1, - }, - scrollTop: 0, - scrollLeft: 0, - defaultColumnWidth: 88, - defaultRowHeight: 24, - mergeData: [], - cellData: {}, - rowData: {}, - columnData: {}, - showGridlines: 1, - rowHeader: { - width: 46, - hidden: 0, - }, - columnHeader: { - height: 20, - hidden: 0, - }, - rightToLeft: 0, - }, - }, - resources: [ - { - name: 'SHEET_DATA_VALIDATION_PLUGIN', - data: '{"xeyZKmZIRXwhSzwbec9-c":[{"uid":"VvFh9x","type":"date","operator":"greaterThan","formula1":"2020-11-11","ranges":[{"startRow":0,"startColumn":0,"endRow":0,"endColumn":0,"rangeType":0}],"formula2":""},{"uid":"K2P_rg","type":"date","operator":"greaterThanOrEqual","formula1":"2025-11-12","ranges":[{"startRow":0,"startColumn":1,"endRow":0,"endColumn":1,"rangeType":0,"unitId":"fbpG8l","sheetId":"xeyZKmZIRXwhSzwbec9-c"}],"formula2":"","bizInfo":{"showTime":true}}]}', - }, - ], - }); + univer.createUnit(UniverInstanceType.UNIVER_SHEET, DEFAULT_WORKBOOK_DATA_DEMO); } setTimeout(() => { diff --git a/packages/engine-render/src/base-object.ts b/packages/engine-render/src/base-object.ts index 3e723a2222..3abe6ee965 100644 --- a/packages/engine-render/src/base-object.ts +++ b/packages/engine-render/src/base-object.ts @@ -543,6 +543,7 @@ export abstract class BaseObject extends Disposable { } /** + * Similar to setProps, but only props in BASE_OBJECT_ARRAY_Set * this[pKey] = option[pKey] * @param option */ diff --git a/packages/engine-render/src/scene.input-manager.ts b/packages/engine-render/src/scene.input-manager.ts index e8f6e6e308..ef3c358dbd 100644 --- a/packages/engine-render/src/scene.input-manager.ts +++ b/packages/engine-render/src/scene.input-manager.ts @@ -429,6 +429,9 @@ export class InputManager extends Disposable { * @param offsetY */ private _getObjectAtPos(offsetX: number, offsetY: number) { + if (!this._scene.objectsEvented) { + return null; + } return this._scene?.pick(Vector2.FromArray([offsetX, offsetY])); } @@ -460,6 +463,13 @@ export class InputManager extends Disposable { // } + /** + * If return true, the event will be dispatched to Scene + * Even Scene is not a object, but scene could handle event. + * currentObject is null, return true + * @param currentObject + * @returns boolean + */ private _shouldDispatchEventToScene(currentObject: Nullable): boolean { // 1. Check for empty object if (currentObject == null) { diff --git a/packages/engine-render/src/scene.ts b/packages/engine-render/src/scene.ts index e57b1a6755..9fb4da3336 100644 --- a/packages/engine-render/src/scene.ts +++ b/packages/engine-render/src/scene.ts @@ -480,6 +480,11 @@ export class Scene extends Disposable { return this._layers; } + /** + * Get layer by zIndex. + * This method would create a new Layer if Layer is not exist. + * @param zIndex + */ getLayer(zIndex: number = 1): Layer { for (const layer of this._layers) { if (layer.zIndex === zIndex) { @@ -516,7 +521,8 @@ export class Scene extends Disposable { } /** - * Add objects to Layer( Layer is specfied by zIndex) + * Add objects to Layer( Layer is specified by zIndex) + * This method would create a new Layer if Layer is not exist. * If object is a group, insert all its children and group itself to _objects[]. * @param objects * @param zIndex @@ -530,7 +536,8 @@ export class Scene extends Disposable { /** * Add object to Layer (Layer is specified by zIndex). - * If object is a group, insert all its children and group itself to _objects[]. + * This method would create a new Layer if Layer is not exist. + * And if object is a group, insert all its children and group itself to _objects[]. * @param o * @param zIndex layer index * @returns {Scene} scene @@ -988,7 +995,9 @@ export class Scene extends Disposable { const scrollBarRect = pickedViewport.pickScrollBar(coord); if (scrollBarRect) { - return scrollBarRect; + if (scrollBarRect.evented) { + return scrollBarRect; + } } const vecFromSheetContent = pickedViewport.transformVector2SceneCoord(coord); @@ -1258,8 +1267,9 @@ export class Scene extends Disposable { } /** + * Disable scene.event * If scene.event is disabled, scene.pick(cursor Pos) return null. - * Then only scene itself can response to pointer event, all objects under the scene would not. + * After disableObjectsEvent, only the scene itself can respond to pointer events; objects beneath the scene will not. * see sceneInputManager@_onPointerMove */ // 禁用对象事件 diff --git a/packages/engine-render/src/shape/scroll-bar.ts b/packages/engine-render/src/shape/scroll-bar.ts index d6a21324e6..d6d1b9b499 100644 --- a/packages/engine-render/src/shape/scroll-bar.ts +++ b/packages/engine-render/src/shape/scroll-bar.ts @@ -62,56 +62,64 @@ const DEFAULT_THUMB_MARGIN = 2; const HOVER_THUMB_MARGIN = 1; export class ScrollBar extends Disposable { - _enableHorizontal: boolean = true; - _enableVertical: boolean = true; + private _enableHorizontal: boolean = true; + private _enableVertical: boolean = true; - horizontalThumbSize: number = 0; - horizontalMinusMiniThumb: number = 0; - horizontalTrackWidth: number = 0; - horizonScrollTrack: Nullable; - horizonThumbRect: Nullable; + private _horizontalMetrics = { + thumbSize: 0, + minusMiniThumb: 0, + trackWidth: 0, + }; - verticalThumbSize: number = 0; - verticalTrackHeight: number = 0; - verticalMinusMiniThumb: number = 0; - verticalScrollTrack: Nullable; - verticalThumbRect: Nullable; + private _verticalMetrics = { + thumbSize: 0, + minusMiniThumb: 0, + trackHeight: 0, + }; - placeholderBarRect: Nullable; + private _scrollElements = { + horizonTrack: null as Nullable, + horizonThumb: null as Nullable, + verticalTrack: null as Nullable, + verticalThumb: null as Nullable, + placeholder: null as Nullable, + }; protected _viewport!: Viewport; private _mainScene: Nullable; - private _lastX: number = -1; - private _lastY: number = -1; + private _pointerState = { + lastX: -1, + lastY: -1, + isHorizonMove: false, + isVerticalMove: false, + }; - private _isHorizonMove = false; - private _isVerticalMove = false; + private _subscriptions = { + horizonMove: null as Nullable, + horizonUp: null as Nullable, + verticalMove: null as Nullable, + verticalUp: null as Nullable, + }; - private _horizonPointerMoveSub: Nullable; - private _horizonPointerUpSub: Nullable; - private _verticalPointerMoveSub: Nullable; - private _verticalPointerUpSub: Nullable; - - private _thumbDefaultBackgroundColor = 'rgba(24, 28, 42, 0.20)'; - private _thumbHoverBackgroundColor = 'rgba(24, 28, 42, 0.30)'; - private _thumbActiveBackgroundColor = 'rgba(24, 28, 42, 0.40)'; - private _trackBackgroundColor = 'rgba(255,255,255,0.5)'; - private _trackBorderColor = 'rgba(255,255,255,0.7)'; + private _colors = { + thumbDefault: 'rgba(24, 28, 42, 0.20)', + thumbHover: 'rgba(24, 28, 42, 0.30)', + thumbActive: 'rgba(24, 28, 42, 0.40)', + trackBackground: 'rgba(255,255,255,0.5)', + trackBorder: 'rgba(255,255,255,0.7)', + }; /** * The thickness of a scrolling track * ThumbSize = trackSize - thumbMargin * 2 */ private _trackThickness: number = DEFAULT_TRACK_SIZE; - // private _hTrackThickness: number = DEFAULT_TRACK_SIZE; - // private _vTrackThickness: number = DEFAULT_TRACK_SIZE; /** * The margin between thumb and bar. * ThumbSize = barSize - thumbMargin * 2 */ - // private _thumbMargin = DEFAULT_THUMB_MARGIN; private _vThumbMargin = DEFAULT_THUMB_MARGIN; private _hThumbMargin = DEFAULT_THUMB_MARGIN; @@ -137,9 +145,10 @@ export class ScrollBar extends Disposable { } this.setProps(props); this._viewport = view; + this._mainScene = view.scene; this._initialScrollRect(); - this._initialVerticalEvent(); this._initialHorizontalEvent(); + this._initialVerticalEvent(); this._viewport.setScrollBar(this); } @@ -160,7 +169,7 @@ export class ScrollBar extends Disposable { }); if (Tools.isDefine(props.thumbBackgroundColor)) { - this._thumbDefaultBackgroundColor = props.thumbBackgroundColor; + this._colors.thumbDefault = props.thumbBackgroundColor; } if (Tools.isDefine(props.barSize)) { @@ -177,6 +186,14 @@ export class ScrollBar extends Disposable { } } + get verticalScrollTrack() { + return this._scrollElements.verticalTrack; + } + + get horizonScrollTrack() { + return this._scrollElements.horizonTrack; + } + get enableHorizontal() { return this._enableHorizontal; } @@ -194,97 +211,89 @@ export class ScrollBar extends Disposable { } get limitX() { - if (!this.horizonThumbRect?.visible) { + if (!this._scrollElements.horizonThumb?.visible) { return 0; } return this.horizontalTrackWidth - this.horizontalThumbSize; } get limitY() { - if (!this.verticalThumbRect?.visible) { + if (!this._scrollElements.verticalThumb?.visible) { return 0; } return this.verticalTrackHeight - this.verticalThumbSize; } - get ratioScrollX(): number { - if ( - this._enableHorizontal === false || - this.horizontalThumbSize === undefined || - this.horizontalTrackWidth === undefined - ) { - return 1; - } - - const ratio = ( - ((this.horizontalThumbSize - this.horizontalMinusMiniThumb) * this.miniThumbRatioX) / - this.horizontalTrackWidth - ); - - if (Number.isNaN(ratio)) { - return 1; - } else { - return ratio; - } + get ratioScrollX() { + if (!this._enableHorizontal) return 1; + const { thumbSize, minusMiniThumb, trackWidth } = this._horizontalMetrics; + if (thumbSize === undefined || trackWidth === undefined) return 0; + return thumbSize / (trackWidth - minusMiniThumb); } - get ratioScrollY(): number { - if ( - this._enableVertical === false || - this.verticalThumbSize === undefined || - this.verticalTrackHeight === undefined - ) { - return 1; - } - const ratio = ( - ((this.verticalThumbSize - this.verticalMinusMiniThumb) * this.miniThumbRatioY) / this.verticalTrackHeight - ); - - if (Number.isNaN(ratio)) { - return 1; - } else { - return ratio; - } + get ratioScrollY() { + if (!this._enableVertical) return 1; + const { thumbSize, minusMiniThumb, trackHeight } = this._verticalMetrics; + if (thumbSize === undefined || trackHeight === undefined) return 0; + return thumbSize / (trackHeight - minusMiniThumb); } + // get ratioX() { + // if (!this._enableHorizontal) return 0; + // const { thumbSize, minusMiniThumb, trackWidth } = this._horizontalMetrics; + // const { horizonThumb } = this._scrollElements; + // if (!horizonThumb?.getState()) return 0; + // return horizonThumb.getState().left / (trackWidth - minusMiniThumb); + // } + + // get ratioY() { + // if (!this._enableVertical) return 0; + // const { thumbSize, minusMiniThumb, trackHeight } = this._verticalMetrics; + // const { verticalThumb } = this._scrollElements; + // if (!verticalThumb?.getState()) return 0; + // return verticalThumb.getState().top / (trackHeight - minusMiniThumb); + // } + get miniThumbRatioX() { - const limit = this.horizontalTrackWidth - this.horizontalThumbSize; - - if (limit === 0) { - return 0; - } - - const actual = this.horizontalTrackWidth - (this.horizontalThumbSize - this.horizontalMinusMiniThumb); - - if (actual === 0) { - return 0; - } - - return limit / actual; + if (!this._enableHorizontal) return 0; + const { thumbSize, minusMiniThumb, trackWidth } = this._horizontalMetrics; + if (thumbSize === undefined || trackWidth === undefined) return 0; + return (thumbSize - minusMiniThumb) / trackWidth; } get miniThumbRatioY() { - const limit = this.verticalTrackHeight - this.verticalThumbSize; + if (!this._enableVertical) return 0; + const { thumbSize, minusMiniThumb, trackHeight } = this._verticalMetrics; + if (thumbSize === undefined || trackHeight === undefined) return 0; + return (thumbSize - minusMiniThumb) / trackHeight; + } - if (limit === 0) { - return 0; - } + get horizontalTrackWidth() { + if (!this._enableHorizontal) return 0; + return this._viewportW - this._trackThickness; + } - const actual = this.verticalTrackHeight - (this.verticalThumbSize - this.verticalMinusMiniThumb); + get horizontalThumbSize() { + if (!this._enableHorizontal) return 0; + return Math.max(MIN_THUMB_SIZE, this._viewportW * (this._viewportW / this._contentW)); + } - if (actual === 0) { - return 0; - } + get verticalTrackHeight() { + if (!this._enableVertical) return 0; + return this._viewportH - this._trackThickness; + } - return limit / actual; + get verticalThumbSize() { + if (!this._enableVertical) return 0; + return Math.max(MIN_THUMB_SIZE, this._viewportH * (this._viewportH / this._contentH)); } hasHorizonThumb() { - return this.horizonThumbRect?.visible || false; + return this._scrollElements.horizonThumb?.visible || false; } hasVerticalThumb() { - return this.verticalThumbRect?.visible || false; + return this._scrollElements.verticalThumb?.visible || false; } get scrollHorizonThumbThickness() { @@ -317,164 +326,184 @@ export class ScrollBar extends Disposable { override dispose() { super.dispose(); - this.horizonScrollTrack?.dispose(); - this.horizonThumbRect?.dispose(); - this.verticalScrollTrack?.dispose(); - this.verticalThumbRect?.dispose(); - this.placeholderBarRect?.dispose(); + const { horizonTrack, horizonThumb, verticalTrack, verticalThumb, placeholder } = this._scrollElements; + const { horizonMove, horizonUp, verticalMove, verticalUp } = this._subscriptions; - this.horizonScrollTrack = null; - this.horizonThumbRect = null; - this.verticalScrollTrack = null; - this.verticalThumbRect = null; - this.placeholderBarRect = null; + // Dispose scroll elements + horizonTrack?.dispose(); + horizonThumb?.dispose(); + verticalTrack?.dispose(); + verticalThumb?.dispose(); + placeholder?.dispose(); - this._horizonPointerMoveSub?.unsubscribe(); - this._horizonPointerUpSub?.unsubscribe(); - this._verticalPointerMoveSub?.unsubscribe(); - this._verticalPointerUpSub?.unsubscribe(); + // Clear references + this._scrollElements = { + horizonTrack: null, + horizonThumb: null, + verticalTrack: null, + verticalThumb: null, + placeholder: null, + }; + + // Cleanup subscriptions + horizonMove?.unsubscribe(); + horizonUp?.unsubscribe(); + verticalMove?.unsubscribe(); + verticalUp?.unsubscribe(); this._eventSub.unsubscribe(); + this._mainScene = null; this._viewport.removeScrollBar(); } render(ctx: UniverRenderingContext, left: number = 0, top: number = 0) { const { scrollX, scrollY } = this._viewport; + const { horizonTrack, horizonThumb, verticalTrack, verticalThumb, placeholder } = this._scrollElements; + ctx.save(); const transform = new Transform([1, 0, 0, 1, left, top]); const m = transform.getMatrix(); ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - if (this._enableHorizontal) { - this.horizonScrollTrack!.render(ctx); - this.horizonThumbRect!.translate(scrollX).render(ctx); + + if (this._enableHorizontal && horizonTrack && horizonThumb) { + horizonTrack.render(ctx); + horizonThumb.translate(scrollX).render(ctx); } - if (this._enableVertical) { - this.verticalScrollTrack!.render(ctx); - this.verticalThumbRect!.translate(undefined, scrollY).render(ctx); + if (this._enableVertical && verticalTrack && verticalThumb) { + verticalTrack.render(ctx); + verticalThumb.translate(undefined, scrollY).render(ctx); } - if (this._enableHorizontal && this._enableVertical) { - this.placeholderBarRect!.render(ctx); + if (this._enableHorizontal && this._enableVertical && placeholder) { + placeholder.render(ctx); } ctx.restore(); } private _resizeHorizontal() { + if (!this._enableHorizontal) return; + + const { horizonTrack, horizonThumb } = this._scrollElements; const viewportH = this._viewportH; const viewportW = this._viewportW; const contentWidth = this._contentW; - // ratioScrollY = 内容可视区高度/内容实际区高度= 滑动条的高度/滑道高度=滚动条的顶部距离/实际内容区域顶部距离; - if (!this._enableHorizontal) { - return; - } + // Reset metrics + this._horizontalMetrics.minusMiniThumb = 0; + this._horizontalMetrics.trackWidth = viewportW - + (this._enableVertical ? this._trackThickness : 0) - + this._trackBorderThickness; - this.horizontalMinusMiniThumb = 0; - this.horizontalTrackWidth = viewportW - (this._enableVertical ? this._trackThickness : 0) - this._trackBorderThickness; - - this.horizontalThumbSize = - ((this.horizontalTrackWidth * (this.horizontalTrackWidth - this._trackBorderThickness)) / contentWidth) * + // Calculate thumb size + this._horizontalMetrics.thumbSize = + ((this._horizontalMetrics.trackWidth * + (this._horizontalMetrics.trackWidth - this._trackBorderThickness)) / contentWidth) * this._thumbLengthRatio; - // this._horizontalThumbWidth = this._horizontalThumbWidth < MINI_THUMB_SIZE ? MINI_THUMB_SIZE : this._horizontalThumbWidth; - if (this.horizontalThumbSize < this._minThumbSizeH) { - this.horizontalMinusMiniThumb = this._minThumbSizeH - this.horizontalThumbSize; - this.horizontalThumbSize = this._minThumbSizeH; + // Enforce minimum thumb size + if (this._horizontalMetrics.thumbSize < this._minThumbSizeH) { + this._horizontalMetrics.minusMiniThumb = this._minThumbSizeH - this._horizontalMetrics.thumbSize; + this._horizontalMetrics.thumbSize = this._minThumbSizeH; } - this.horizonScrollTrack?.transformByState({ + // Update track position and size + horizonTrack?.transformByState({ left: 0, top: viewportH - this._trackThickness, - width: this.horizontalTrackWidth, + width: this._horizontalMetrics.trackWidth, height: Math.max(0, this._trackThickness - this._trackBorderThickness), }); - // content is smaller than viewport size - if (this.horizontalThumbSize >= viewportW - (this._trackThickness + 2)) { - this.horizonThumbRect?.setProps({ - visible: false, - }); + // Handle thumb visibility and position + if (this._horizontalMetrics.thumbSize >= viewportW - (this._trackThickness + 2)) { + horizonThumb?.setProps({ visible: false }); } else { - if (!this.horizonThumbRect?.visible) { - this.horizonThumbRect?.setProps({ - visible: true, - }); + if (!horizonThumb?.visible) { + horizonThumb?.setProps({ visible: true }); } - this.horizonThumbRect?.transformByState({ + horizonThumb?.transformByState({ left: this._viewport.scrollX, top: viewportH - this._trackThickness + this._hThumbMargin, - width: this.horizontalThumbSize, + width: this._horizontalMetrics.thumbSize, height: this.scrollHorizonThumbThickness, }); } } private _resizeVertical() { + if (!this._enableVertical) return; + + const { verticalTrack, verticalThumb } = this._scrollElements; const viewportH = this._viewportH; const viewportW = this._viewportW; const contentHeight = this._contentH; - if (!this._enableVertical) { - return; + // Reset metrics + this._verticalMetrics.minusMiniThumb = 0; + this._verticalMetrics.trackHeight = viewportH - + (this._enableHorizontal ? this._trackThickness : 0) - + this._trackBorderThickness; + + // Calculate thumb size + this._verticalMetrics.thumbSize = + ((this._verticalMetrics.trackHeight * + this._verticalMetrics.trackHeight) / contentHeight) * + this._thumbLengthRatio; + + // Enforce minimum thumb size + if (this._verticalMetrics.thumbSize < this._minThumbSizeV) { + this._verticalMetrics.minusMiniThumb = this._minThumbSizeV - this._verticalMetrics.thumbSize; + this._verticalMetrics.thumbSize = this._minThumbSizeV; } - this.verticalMinusMiniThumb = 0; - this.verticalTrackHeight = viewportH - (this._enableHorizontal ? this._trackThickness : 0) - this._trackBorderThickness; - this.verticalThumbSize = - ((this.verticalTrackHeight * this.verticalTrackHeight) / contentHeight) * this._thumbLengthRatio; - // this._verticalThumbHeight = this._verticalThumbHeight < MINI_THUMB_SIZE ? MINI_THUMB_SIZE : this._verticalThumbHeight; - if (this.verticalThumbSize < this._minThumbSizeV) { - this.verticalMinusMiniThumb = this._minThumbSizeV - this.verticalThumbSize; - this.verticalThumbSize = this._minThumbSizeV; - } - - this.verticalScrollTrack?.transformByState({ + // Update track position and size + verticalTrack?.transformByState({ left: viewportW - this._trackThickness, top: 0, width: Math.max(0, this._trackThickness - this._trackBorderThickness), - height: this.verticalTrackHeight, + height: this._verticalMetrics.trackHeight, }); - // content is smaller than viewport size - if (this.verticalThumbSize >= viewportH - this._trackThickness) { - this.verticalThumbRect?.setProps({ - visible: false, - }); + // Handle thumb visibility and position + if (this._verticalMetrics.thumbSize >= viewportH - this._trackThickness) { + verticalThumb?.setProps({ visible: false }); } else { - if (!this.verticalThumbRect?.visible) { - this.verticalThumbRect?.setProps({ - visible: true, - }); + if (!verticalThumb?.visible) { + verticalThumb?.setProps({ visible: true }); } - this.verticalThumbRect?.transformByState({ + verticalThumb?.transformByState({ left: viewportW - this._trackThickness + this._vThumbMargin, top: this._viewport.scrollY, width: this.scrollVerticalThumbThickness, - height: this.verticalThumbSize, + height: this._verticalMetrics.thumbSize, }); } } private _resizeRightBottomCorner() { + if (!this._enableHorizontal || !this._enableVertical) return; + + const { placeholder } = this._scrollElements; const viewportH = this._viewportH; const viewportW = this._viewportW; - if (this._enableHorizontal && this._enableVertical) { - this.placeholderBarRect?.transformByState({ - left: viewportW - this._trackThickness, - top: viewportH - this._trackThickness, - width: Math.max(0, this._trackThickness - this._trackBorderThickness), - height: Math.max(0, this._trackThickness - this._trackBorderThickness), - }); - } + const thickness = Math.max(0, this._trackThickness - this._trackBorderThickness); + + placeholder?.transformByState({ + left: viewportW - this._trackThickness, + top: viewportH - this._trackThickness, + width: thickness, + height: thickness, + }); } private _viewportH = 0; private _viewportW = 0; private _contentW = 0; private _contentH = 0; + /** * Adjust scroll track & thumb size based on the viewport size. * @param viewportWidth @@ -503,11 +532,13 @@ export class ScrollBar extends Disposable { } makeDirty(state: boolean) { - this.horizonScrollTrack?.makeDirty(state); - this.horizonThumbRect?.makeDirty(state); - this.verticalScrollTrack?.makeDirty(state); - this.verticalThumbRect?.makeDirty(state); - this.placeholderBarRect?.makeDirty(state); + const { horizonTrack, horizonThumb, verticalTrack, verticalThumb, placeholder } = this._scrollElements; + + horizonTrack?.makeDirty(state); + horizonThumb?.makeDirty(state); + verticalTrack?.makeDirty(state); + verticalThumb?.makeDirty(state); + placeholder?.makeDirty(state); this.makeViewDirty(state); } @@ -518,245 +549,290 @@ export class ScrollBar extends Disposable { } pick(coord: Vector2) { - if (this.horizonThumbRect?.isHit(coord)) { - return this.horizonThumbRect; + if (this._scrollElements.horizonThumb?.isHit(coord)) { + return this._scrollElements.horizonThumb; } - if (this.verticalThumbRect?.isHit(coord)) { - return this.verticalThumbRect; + if (this._scrollElements.verticalThumb?.isHit(coord)) { + return this._scrollElements.verticalThumb; } - if (this.horizonScrollTrack?.isHit(coord)) { - return this.horizonScrollTrack; + if (this._scrollElements.horizonTrack?.isHit(coord)) { + return this._scrollElements.horizonTrack; } - if (this.verticalScrollTrack?.isHit(coord)) { - return this.verticalScrollTrack; + if (this._scrollElements.verticalTrack?.isHit(coord)) { + return this._scrollElements.verticalTrack; + } + + if (this._scrollElements.verticalThumb?.isHit(coord)) { + return this._scrollElements.verticalThumb; + } + + if (this._scrollElements.horizonTrack?.isHit(coord)) { + return this._scrollElements.horizonTrack; + } + + if (this._scrollElements.verticalTrack?.isHit(coord)) { + return this._scrollElements.verticalTrack; } return null; } private _initialScrollRect() { + const { thumbDefault, trackBackground, trackBorder } = this._colors; + + const trackConfig = { + fill: trackBackground, + strokeWidth: this._trackBorderThickness, + stroke: trackBorder, + }; + + const thumbConfig = { + fill: thumbDefault, + strokeWidth: 0, + }; + if (this._enableHorizontal) { - this.horizonScrollTrack = new Rect('__horizonBarRect__', { - fill: this._trackBackgroundColor!, - strokeWidth: this._trackBorderThickness, - stroke: this._trackBorderColor!, + const horizonTrack = new Rect('__horizonBarRect__', { + ...trackConfig, + width: this._viewportW, + height: this._trackThickness, + left: 0, + top: this._viewportH - this._trackThickness, }); - this.horizonThumbRect = new Rect('__horizonThumbRect__', { + const horizonThumb = new Rect('__horizonThumbRect__', { + ...thumbConfig, radius: 6, - fill: this._thumbDefaultBackgroundColor!, + width: this.horizontalThumbSize, + height: this.scrollHorizonThumbThickness, + left: 0, + top: this._viewportH - this._trackThickness + this._hThumbMargin, }); + + this._scrollElements.horizonTrack = horizonTrack; + this._scrollElements.horizonThumb = horizonThumb; } if (this._enableVertical) { - this.verticalScrollTrack = new Rect('__verticalBarRect__', { - fill: this._trackBackgroundColor!, - strokeWidth: this._trackBorderThickness, - stroke: this._trackBorderColor!, + const verticalTrack = new Rect('__verticalBarRect__', { + ...trackConfig, + width: this._trackThickness, + height: this._viewportH, + left: this._viewportW - this._trackThickness, + top: 0, }); - this.verticalThumbRect = new Rect('__verticalThumbRect__', { + const verticalThumb = new Rect('__verticalThumbRect__', { + ...thumbConfig, radius: 6, - fill: this._thumbDefaultBackgroundColor!, + width: this.scrollVerticalThumbThickness, + height: this.verticalThumbSize, + left: this._viewportW - this._trackThickness + this._vThumbMargin, + top: 0, }); - } - if (this._enableHorizontal && this._enableVertical) { - this.placeholderBarRect = new Rect('__placeholderBarRect__', { - fill: this._trackBackgroundColor!, - strokeWidth: this._trackBorderThickness, - stroke: this._trackBorderColor!, - }); + this._scrollElements.verticalTrack = verticalTrack; + this._scrollElements.verticalThumb = verticalThumb; } } + private _setHorizonEvented(evented: boolean) { + this._scrollElements.horizonThumb && (this._scrollElements.horizonThumb.evented = evented); + this._scrollElements.horizonTrack && (this._scrollElements.horizonTrack.evented = evented); + } + + private _setVerticalEvented(evented: boolean) { + this._scrollElements.verticalThumb && (this._scrollElements.verticalThumb.evented = evented); + this._scrollElements.verticalTrack && (this._scrollElements.verticalTrack.evented = evented); + } + private _initialVerticalEvent() { - if (!this._enableVertical) { - return; - } - const mainScene = this._mainScene || this._viewport.scene; - - if (this.verticalThumbRect) { - this._eventSub.add(this.verticalThumbRect.onPointerEnter$.subscribeEvent((evt: unknown, state: EventState) => { - this._verticalHoverFunc(this._thumbHoverBackgroundColor!, evt, state); - })); - } - if (this.verticalThumbRect) { - this._eventSub.add(this.verticalThumbRect.onPointerLeave$.subscribeEvent((evt: unknown, state: EventState) => { - this._verticalHoverLeaveFunc(this._thumbDefaultBackgroundColor!, evt, state); - })); - } - - // events for pointerdown at scroll track - if (this.verticalScrollTrack) { - this._eventSub.add(this.verticalScrollTrack.onPointerDown$.subscribeEvent((evt: unknown, state: EventState) => { - const e = evt as IPointerEvent | IMouseEvent; - this._viewport.scrollToBarPos({ - y: e.offsetY - this._viewport.top - this.verticalThumbSize / 2, - }); - - state.stopPropagation(); - })); - } + const { thumbDefault, thumbHover, thumbActive } = this._colors; // drag events for vertical scrollbar // scene.input-manager@_onPointerDown --> base-object@triggerPointerDown! - if (this.verticalThumbRect) { - this._eventSub.add(this.verticalThumbRect.onPointerDown$.subscribeEvent((evt: unknown, state: EventState) => { + if (this._scrollElements.verticalThumb) { + const verticalThumb = this._scrollElements.verticalThumb; + + // Pointer enter event + this._eventSub.add(verticalThumb.onPointerEnter$.subscribeEvent((evt: unknown, state: EventState) => { + this._verticalHoverFunc(thumbHover, evt, state); + })); + + // Pointer leave event + this._eventSub.add(verticalThumb.onPointerLeave$.subscribeEvent((evt: unknown, state: EventState) => { + if (verticalThumb.evented) { + this._verticalHoverLeaveFunc(thumbDefault, evt, state); + } + })); + + // Pointer down event + this._eventSub.add(verticalThumb.onPointerDown$.subscribeEvent((evt: unknown, state: EventState) => { const e = evt as IPointerEvent | IMouseEvent; - const srcElement = this.verticalThumbRect; - this._isVerticalMove = true; - this._lastX = e.offsetX; - this._lastY = e.offsetY; - // srcElement.fill = this._thumbHoverBackgroundColor!; - srcElement?.setProps({ - fill: this._thumbActiveBackgroundColor!, - }); - mainScene.setCaptureObject(this.verticalThumbRect!); - mainScene.disableObjectsEvent(); + this._pointerState.isVerticalMove = true; + this._pointerState.lastX = e.offsetX; + this._pointerState.lastY = e.offsetY; + + verticalThumb.setProps({ fill: thumbActive }); + this._setVerticalEvented(false); + mainScene.setCaptureObject(verticalThumb); this.makeViewDirty(true); state.stopPropagation(); })); } - // pointer down then move on scrollbar - this._verticalPointerMoveSub = mainScene.onPointerMove$.subscribeEvent((evt: unknown, _state: EventState) => { + // Handle pointer move events + this._subscriptions.verticalMove = mainScene.onPointerMove$.subscribeEvent((evt: unknown, _state: EventState) => { const e = evt as IPointerEvent | IMouseEvent; - if (!this._isVerticalMove) { - return; - } + if (!this._pointerState.isVerticalMove) return; + this._viewport.scrollByBarDeltaValue({ - y: e.offsetY - this._lastY, + y: e.offsetY - this._pointerState.lastY, }); - this._lastY = e.offsetY; + this._pointerState.lastY = e.offsetY; mainScene.getEngine()?.setCapture(); }); - this._verticalPointerUpSub = mainScene.onPointerUp$.subscribeEvent((_evt: unknown, _state: EventState) => { - const srcElement = this.verticalThumbRect; - this._isVerticalMove = false; + // Handle pointer up events + this._subscriptions.verticalUp = mainScene.onPointerUp$.subscribeEvent((evt: unknown, state: EventState) => { + const { verticalThumb } = this._scrollElements; + const { thumbHover } = this._colors; + + this._pointerState.isVerticalMove = false; + this._verticalHoverLeaveFunc(thumbDefault, evt, state); mainScene.releaseCapturedObject(); mainScene.enableObjectsEvent(); - srcElement?.setProps({ - fill: this._thumbHoverBackgroundColor!, + if (verticalThumb) { + verticalThumb.setProps({ fill: thumbHover }); + this._setVerticalEvented(true); + } + this.makeViewDirty(true); + }); + } + + private _initialHorizontalEvent() { + if (!this._enableHorizontal) return; + + const mainScene = this._mainScene || this._viewport.scene; + const { horizonThumb, horizonTrack } = this._scrollElements; + const { thumbHover, thumbDefault, thumbActive } = this._colors; + + // Thumb hover events + if (horizonThumb) { + this._eventSub.add( + horizonThumb.onPointerEnter$.subscribeEvent((evt: unknown, state: EventState) => { + this._horizonHoverFunc(thumbHover, evt, state); + }) + ); + + this._eventSub.add( + horizonThumb.onPointerLeave$.subscribeEvent((evt: unknown, state: EventState) => { + if (horizonThumb.evented) { + this._horizonHoverLeaveFunc(thumbDefault, evt, state); + } + }) + ); + } + + // Track click events + if (horizonTrack) { + this._eventSub.add( + horizonTrack.onPointerDown$.subscribeEvent((evt: unknown, state: EventState) => { + const e = evt as IPointerEvent | IMouseEvent; + this._viewport.scrollToBarPos({ + x: e.offsetX - this._viewport.left - this._horizontalMetrics.thumbSize / 2, + }); + state.stopPropagation(); + }) + ); + } + + // Thumb drag events + if (horizonThumb) { + this._eventSub.add( + horizonThumb.onPointerDown$.subscribeEvent((evt: unknown, state: EventState) => { + const e = evt as IPointerEvent | IMouseEvent; + this._pointerState.isHorizonMove = true; + this._pointerState.lastX = e.offsetX; + this._pointerState.lastY = e.offsetY; + + horizonThumb.setProps({ fill: thumbActive }); + this._setHorizonEvented(false); + mainScene.setCaptureObject(horizonThumb); + // mainScene.disableObjectsEvent(); + this.makeViewDirty(true); + state.stopPropagation(); + }) + ); + } + // pointer down then move on scrollbar + this._subscriptions.horizonMove = mainScene.onPointerMove$.subscribeEvent((evt: unknown, _state: EventState) => { + const e = evt as IPointerEvent | IMouseEvent; + if (!this._pointerState.isHorizonMove) { + return; + } + this._viewport.scrollByBarDeltaValue({ + x: e.offsetX - this._pointerState.lastX, }); + this._pointerState.lastX = e.offsetX; + mainScene.getEngine()?.setCapture(); + }); + this._subscriptions.horizonUp = mainScene.onPointerUp$.subscribeEvent((evt: unknown, state: EventState) => { + this._pointerState.isHorizonMove = false; + mainScene.releaseCapturedObject(); + mainScene.enableObjectsEvent(); + this._horizonHoverLeaveFunc(thumbDefault, evt, state); + this._scrollElements.horizonThumb?.setProps({ + fill: thumbDefault, + }); + this._setHorizonEvented(true); this.makeViewDirty(true); }); } private _horizonHoverFunc(color: string, evt: unknown, state: EventState) { - // this._trackThickness = HOVER_TRACK_SIZE; + const { horizonThumb } = this._scrollElements; this._hThumbMargin = HOVER_THUMB_MARGIN; this._resizeHorizontal(); this._resizeRightBottomCorner(); - this._hoverFunc(color, this.horizonThumbRect!)(evt, state); + this._hoverFunc(color, horizonThumb!)(evt, state); } private _horizonHoverLeaveFunc(color: string, evt: unknown, state: EventState) { - // this._trackThickness = DEFAULT_TRACK_SIZE; + const { horizonThumb } = this._scrollElements; this._hThumbMargin = DEFAULT_THUMB_MARGIN; this._resizeHorizontal(); this._resizeRightBottomCorner(); - this._hoverFunc(color, this.horizonThumbRect!)(evt, state); + this._hoverFunc(color, horizonThumb!)(evt, state); } - private _verticalHoverFunc(color: string, evt: unknown, state: EventState) { + private _verticalHoverFunc = (color: string, evt: unknown, state: EventState) => { + const { verticalThumb } = this._scrollElements; this._vThumbMargin = HOVER_THUMB_MARGIN; this._resizeVertical(); this._resizeRightBottomCorner(); - this._hoverFunc(color, this.verticalThumbRect!)(evt, state); - } + this._hoverFunc(color, verticalThumb!)(evt, state); + }; private _verticalHoverLeaveFunc(color: string, evt: unknown, state: EventState) { + const { verticalThumb } = this._scrollElements; this._vThumbMargin = DEFAULT_THUMB_MARGIN; this._resizeVertical(); this._resizeRightBottomCorner(); - this._hoverFunc(color, this.verticalThumbRect!)(evt, state); + this._hoverFunc(color, verticalThumb!)(evt, state); } private _hoverFunc(color: string, thumb: Rect): (evt: unknown, state: EventState) => void { return (_evt: unknown, _state: EventState) => { - thumb.setProps({ - fill: color, - }); - // this._trackThickness = HOVER_TRACK_SIZE; + thumb.setProps({ fill: color }); + this._trackThickness = HOVER_TRACK_SIZE; this._resizeHorizontal(); this.makeViewDirty(true); }; } - - private _initialHorizontalEvent() { - if (!this._enableHorizontal) { - return; - } - - const mainScene = this._mainScene || this._viewport.scene; - - if (this.horizonThumbRect) { - this._eventSub.add(this.horizonThumbRect.onPointerEnter$.subscribeEvent((evt: unknown, state: EventState) => { - this._horizonHoverFunc(this._thumbHoverBackgroundColor, evt, state); - })); - } - if (this.horizonThumbRect) { - this._eventSub.add(this.horizonThumbRect.onPointerLeave$.subscribeEvent((evt: unknown, state: EventState) => { - this._horizonHoverLeaveFunc(this._thumbDefaultBackgroundColor, evt, state); - })); - } - - // events for pointerdown at scrolltrack - if (this.horizonScrollTrack) { - this._eventSub.add(this.horizonScrollTrack.onPointerDown$.subscribeEvent((evt: unknown, state: EventState) => { - const e = evt as IPointerEvent | IMouseEvent; - this._viewport.scrollToBarPos({ - x: e.offsetX - this._viewport.left - this.horizontalThumbSize / 2, - }); - state.stopPropagation(); - })); - } - - // drag and move event - if (this.horizonThumbRect) { - this._eventSub.add(this.horizonThumbRect.onPointerDown$.subscribeEvent((evt: unknown, state: EventState) => { - const e = evt as IPointerEvent | IMouseEvent; - this._isHorizonMove = true; - this._lastX = e.offsetX; - this._lastY = e.offsetY; - this.horizonThumbRect?.setProps({ - fill: this._thumbActiveBackgroundColor!, - }); - this.makeViewDirty(true); - mainScene.setCaptureObject(this.horizonThumbRect!); - mainScene.disableObjectsEvent(); - state.stopPropagation(); - })); - } - - // pointer down then move on scrollbar - this._horizonPointerMoveSub = mainScene.onPointerMove$.subscribeEvent((evt: unknown, _state: EventState) => { - const e = evt as IPointerEvent | IMouseEvent; - if (!this._isHorizonMove) { - return; - } - this._viewport.scrollByBarDeltaValue({ - x: e.offsetX - this._lastX, - }); - this._lastX = e.offsetX; - mainScene.getEngine()?.setCapture(); - }); - this._horizonPointerUpSub = mainScene.onPointerUp$.subscribeEvent((evt: unknown, state: EventState) => { - ; - this._isHorizonMove = false; - mainScene.releaseCapturedObject(); - mainScene.enableObjectsEvent(); - this.horizonThumbRect?.setProps({ - fill: this._thumbHoverBackgroundColor!, - }); - this.makeViewDirty(true); - }); - } } diff --git a/packages/engine-render/src/shape/shape.ts b/packages/engine-render/src/shape/shape.ts index 843a611202..04a35305da 100644 --- a/packages/engine-render/src/shape/shape.ts +++ b/packages/engine-render/src/shape/shape.ts @@ -346,7 +346,8 @@ export abstract class Shape extends BaseObject { } /** - * if BASE_OBJECT_ARRAY_Set.has(key) not exist, then this[_key] = props[key], + * if props not in BASE_OBJECT_ARRAY_Set then this[_key] = props[key] + * if props in BASE_OBJECT_ARRAY_Set, You should use transformByState * @param props */ setProps(props?: T): Shape { diff --git a/packages/sheets-formula-ui/src/services/render-services/ref-selections.render-service.ts b/packages/sheets-formula-ui/src/services/render-services/ref-selections.render-service.ts index f61c3cc7c8..9bb938cf97 100644 --- a/packages/sheets-formula-ui/src/services/render-services/ref-selections.render-service.ts +++ b/packages/sheets-formula-ui/src/services/render-services/ref-selections.render-service.ts @@ -386,7 +386,7 @@ export class RefSelectionsRenderService extends BaseSelectionRenderService imple this._selectionMoveStart$.next(this.getSelectionDataWithStyle()); - scene.disableObjectsEvent(); + // scene.disableObjectsEvent(); this._clearUpdatingListeners(); this._addEndingListeners(); diff --git a/packages/sheets-ui/src/services/selection/base-selection-render.service.ts b/packages/sheets-ui/src/services/selection/base-selection-render.service.ts index 94eda35cc2..73a7ce6816 100644 --- a/packages/sheets-ui/src/services/selection/base-selection-render.service.ts +++ b/packages/sheets-ui/src/services/selection/base-selection-render.service.ts @@ -751,6 +751,9 @@ export class BaseSelectionRenderService extends Disposable implements ISheetSele } } + /** + * Unsubscribe subjects and enable scene event + */ protected _clearUpdatingListeners(): void { const scene = this._scene; scene.enableObjectsEvent(); diff --git a/packages/sheets-ui/src/services/selection/selection-render.service.ts b/packages/sheets-ui/src/services/selection/selection-render.service.ts index 3aa6376f36..f8417f97a2 100644 --- a/packages/sheets-ui/src/services/selection/selection-render.service.ts +++ b/packages/sheets-ui/src/services/selection/selection-render.service.ts @@ -78,6 +78,8 @@ export class SheetSelectionRenderService extends BaseSelectionRenderService impl const { spreadsheetRowHeader, spreadsheetColumnHeader, spreadsheet, spreadsheetLeftTopPlaceholder } = sheetObject; const { scene } = this._context; + // pointer down ---> selection start + // but pointer move & pointer up for selection end is bind on scene, not spreadsheet. this.disposeWithMe(spreadsheet?.onPointerDown$.subscribeEvent((evt: IPointerEvent | IMouseEvent, state) => { if (this.isSelectionDisabled()) return; if (this.inRefSelectionMode()) return; @@ -432,7 +434,7 @@ export class SheetSelectionRenderService extends BaseSelectionRenderService impl } //#endregion - scene.disableObjectsEvent(); + // scene.disableObjectsEvent(); this._clearUpdatingListeners(); this._addEndingListeners(); scene.getTransformer()?.clearSelectedObjects(); diff --git a/packages/sheets-ui/src/services/selection/selection-shape-extension.ts b/packages/sheets-ui/src/services/selection/selection-shape-extension.ts index 72db0965a0..efc31781ac 100644 --- a/packages/sheets-ui/src/services/selection/selection-shape-extension.ts +++ b/packages/sheets-ui/src/services/selection/selection-shape-extension.ts @@ -351,7 +351,7 @@ export class SelectionShapeExtension { this._scrollTimer = scrollTimer; scrollTimer.startScroll(newEvtOffsetX, newEvtOffsetY, viewMain); - scene.disableObjectsEvent(); + // scene.disableObjectsEvent(); this._scenePointerMoveSub = scene.onPointerMove$.subscribeEvent((moveEvt: IPointerEvent | IMouseEvent) => { if (this._disabled) { @@ -493,7 +493,7 @@ export class SelectionShapeExtension { scrollTimer.startScroll(newEvtOffsetX, newEvtOffsetY, viewMain); this._scrollTimer = scrollTimer; - scene.disableObjectsEvent(); + // scene.disableObjectsEvent(); this._scenePointerMoveSub = scene.onPointerMove$.subscribeEvent((moveEvt: IPointerEvent | IMouseEvent) => { const { offsetX: moveOffsetX, offsetY: moveOffsetY } = moveEvt; @@ -851,7 +851,7 @@ export class SelectionShapeExtension { this._scrollTimer = scrollTimer; - scene.disableObjectsEvent(); + // scene.disableObjectsEvent(); this._controlHandler((o) => { this._fillControlColors.push(o.fill as string);