From 9b2274fe3b33db3c281a7642723ac885307198a9 Mon Sep 17 00:00:00 2001 From: lumix Date: Fri, 7 Feb 2025 17:41:50 +0800 Subject: [PATCH] fix: float dom after freeze (#4587) --- packages/core/src/index.ts | 1 + packages/core/src/sheets/typedef.ts | 7 +- .../sheets/sheet.render-skeleton.ts | 23 ++- packages/engine-render/src/viewport.ts | 11 +- .../canvas-float-dom-manager.service.ts | 139 ++++++++++++------ .../format-painter.controller.ts | 5 +- .../freeze.render-controller.ts | 30 ++++ .../sheet.render-controller.ts | 2 + .../sheet-skeleton-manager.service.ts | 7 +- 9 files changed, 164 insertions(+), 61 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0d76eace4e3..f7ddce93bf1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -204,3 +204,4 @@ export { isNodeEnv } from './shared/tools'; export { Skeleton } from './skeleton.ts'; export type { IGetRowColByPosOptions } from './sheets/sheet-skeleton'; export type { IPosition } from './sheets/typedef.ts'; +export * from './sheets/sheet-skeleton'; diff --git a/packages/core/src/sheets/typedef.ts b/packages/core/src/sheets/typedef.ts index 6e00ff075e6..97bf5dcdad8 100644 --- a/packages/core/src/sheets/typedef.ts +++ b/packages/core/src/sheets/typedef.ts @@ -16,11 +16,12 @@ import type { IResources } from '../services/resource-manager/type'; import type { IObjectArrayPrimitiveType, IObjectMatrixPrimitiveType, Nullable } from '../shared'; +import type { BooleanNumber } from '../types/enum'; import type { LocaleType } from '../types/enum/locale-type'; import type { IDocumentData } from '../types/interfaces'; import type { ICellCustomRender } from '../types/interfaces/i-cell-custom-render'; import type { IStyleData } from '../types/interfaces/i-style-data'; -import { type BooleanNumber, CellValueType } from '../types/enum'; +import { CellValueType } from '../types/enum'; /** * Snapshot of a workbook. @@ -378,12 +379,12 @@ export interface IFreeze { */ ySplit: number; /** - * scrollable start row + * scrollable start row(viewMain start row) */ startRow: number; /** - * scrollable start column + * scrollable start column(viewMain start column) */ startColumn: number; } diff --git a/packages/engine-render/src/components/sheets/sheet.render-skeleton.ts b/packages/engine-render/src/components/sheets/sheet.render-skeleton.ts index c6ea9bab01b..483b75c39fe 100644 --- a/packages/engine-render/src/components/sheets/sheet.render-skeleton.ts +++ b/packages/engine-render/src/components/sheets/sheet.render-skeleton.ts @@ -40,7 +40,7 @@ import type { } from '@univerjs/core'; import type { IDocumentSkeletonColumn } from '../../basics/i-document-skeleton-cached'; import type { ITransformChangeState } from '../../basics/interfaces'; -import type { IBoundRectNoAngle, IViewportInfo } from '../../basics/vector2'; +import type { IBoundRectNoAngle, IPoint, IViewportInfo } from '../../basics/vector2'; import type { Scene } from '../../scene'; import type { BorderCache, IFontCacheItem, IStylesCache } from './interfaces'; import { @@ -1437,6 +1437,27 @@ export class SpreadsheetSkeleton extends SheetSkeleton { }; } + getDistanceFromTopLeft(row: number, col: number): IPoint { + return { + x: this.colStartX(col), + y: this.rowStartY(row), + }; + } + + colStartX(col: number): number { + const arr = this.columnWidthAccumulation; + const i = col - 1; + if (i === -1) return 0; + return arr[i]; + } + + rowStartY(row: number): number { + const arr = this.rowHeightAccumulation; + const i = row - 1; + if (i === -1) return 0; + return arr[i]; + } + getHiddenRowsInRange(range: IRowRange) { const hiddenRows = []; for (let i = range.startRow; i <= range.endRow; i++) { diff --git a/packages/engine-render/src/viewport.ts b/packages/engine-render/src/viewport.ts index 5fd6774051f..677bda31797 100644 --- a/packages/engine-render/src/viewport.ts +++ b/packages/engine-render/src/viewport.ts @@ -206,7 +206,7 @@ export class Viewport { /** * bound of visible area */ - private _viewBound: IBoundRectNoAngle; + private _viewBound: IBoundRectNoAngle = { top: 0, left: 0, bottom: 0, right: 0 }; private _preViewBound: IBoundRectNoAngle; /** @@ -354,8 +354,6 @@ export class Viewport { set viewportScrollY(val: number) { this._viewportScrollY = val; - // const { y } = this.transViewportScroll2ScrollValue(this._viewportScrollX, this._viewportScrollY); - // this.scrollY = y; } get viewportScrollY() { @@ -364,14 +362,10 @@ export class Viewport { set scrollX(val: number) { this._scrollX = val; - // const { x } = this.transScroll2ViewportScrollValue(this._scrollX, this._scrollY); - // this._viewportScrollX = x; } set scrollY(val: number) { this._scrollY = val; - // const { y } = this.transScroll2ViewportScrollValue(this._scrollX, this._scrollY); - // this._viewportScrollY = y; } get scrollX() { @@ -449,6 +443,9 @@ export class Viewport { this._active = false; } + /** + * canvas resize & freeze change would invoke this method + */ resetCanvasSizeAndUpdateScroll() { this._resizeCacheCanvas(); this._updateScrollByViewportScrollValue(); diff --git a/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts b/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts index 18e2688602a..c22c36ad184 100644 --- a/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts +++ b/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts @@ -94,6 +94,11 @@ export interface IDOMAnchor { } export interface ILimitBound extends IBoundRectNoAngle { + /** + * Actually, it means fixed. + * When left is true, dom is fixed to left of dom pos when dom width is shrinking. or dom is fixed to right of dom pos when dom width is shrinking. + * When top is true, dom is fixed to top of dom pos when dom height is shrinking. or dom is fixed to bottom of dom pos when dom height is shrinking. + */ absolute: { left: boolean; top: boolean; @@ -102,17 +107,24 @@ export interface ILimitBound extends IBoundRectNoAngle { /** * Adjust dom bound size when scrolling (dom bound would shrink when scrolling if over the edge of viewMain) - * @param posOfFloatObject + * @param posOfFloatObject The position of float object, relative to sheet content, scale & scrolling does not affect it. * @param scene * @param skeleton * @param worksheet * @returns ILimitBound */ +// eslint-disable-next-line max-lines-per-function export function transformBound2DOMBound(posOfFloatObject: IBoundRectNoAngle, scene: Scene, skeleton: SpreadsheetSkeleton, worksheet: Worksheet, floatDomInfo?: ICanvasFloatDomInfo): ILimitBound { const { scaleX, scaleY } = scene.getAncestorScale(); const viewMain = scene.getViewport(SHEET_VIEWPORT_KEY.VIEW_MAIN); + + const freeze = worksheet.getFreeze(); + const { startColumn: viewMainStartColumn, startRow: viewMainStartRow, xSplit: freezedCol, ySplit: freezedRow } = freeze; + /** + * Actually, it means fixed. + */ const absolute = { - left: true, + left: true, // left means the left of pic is in a viewMainLeft top: true, }; @@ -123,16 +135,20 @@ export function transformBound2DOMBound(posOfFloatObject: IBoundRectNoAngle, sce }; } const { left, right, top, bottom } = posOfFloatObject; - let { top: topBoundOfViewArea, left: leftBoundViewArea, viewportScrollX, viewportScrollY } = viewMain; - + let { top: viewBoundsTop, left: viewBoundsLeft, viewportScrollX, viewportScrollY } = viewMain; // specify edge of viewbound. if not specify, use viewMain. - const { boundsOfViewArea, scrollDirectionResponse } = floatDomInfo || {}; - if (boundsOfViewArea) { + const { boundsOfViewArea: specBoundsOfViewArea, scrollDirectionResponse } = floatDomInfo || {}; + const { rowHeaderWidth, columnHeaderHeight } = skeleton; + const boundsOfViewArea = { + top: columnHeaderHeight, + left: rowHeaderWidth, + }; + if (specBoundsOfViewArea) { if (Tools.isDefine(boundsOfViewArea.top)) { - topBoundOfViewArea = boundsOfViewArea.top; + boundsOfViewArea.top = specBoundsOfViewArea.top; } if (Tools.isDefine(boundsOfViewArea.left)) { - leftBoundViewArea = boundsOfViewArea.left; + boundsOfViewArea.left = specBoundsOfViewArea.left; } } if (scrollDirectionResponse === ScrollDirectionResponse.HORIZONTAL) { @@ -142,51 +158,75 @@ export function transformBound2DOMBound(posOfFloatObject: IBoundRectNoAngle, sce viewportScrollX = 0; } - let offsetLeft: number; - let offsetRight: number; - - // viewMain or viewTop - if (left < leftBoundViewArea) { - absolute.left = true; - offsetLeft = ((leftBoundViewArea) + (left - leftBoundViewArea)) * scaleX; - offsetRight = Math.max( - Math.min( - ((leftBoundViewArea) + (right - leftBoundViewArea)) * scaleX, - (leftBoundViewArea) * scaleX - ), - (right - viewportScrollX) * scaleX); - } else { + let offsetLeft: number = 0; + let offsetRight: number = 0; + + /** + * freezed viewport start & end position + */ + const freezeStartY = skeleton.rowStartY(viewMainStartRow - freezedRow) + columnHeaderHeight; + const freezeStartX = skeleton.colStartX(viewMainStartColumn - freezedCol) + rowHeaderWidth; + const freezeEndY = skeleton.rowStartY(viewMainStartRow) + columnHeaderHeight; + const freezeEndX = skeleton.colStartX(viewMainStartColumn) + rowHeaderWidth; + + if (freezedCol === 0) { absolute.left = false; - offsetLeft = Math.max((left - viewportScrollX) * scaleX, (leftBoundViewArea) * scaleX); - offsetRight = Math.max((right - viewportScrollX) * scaleX, (leftBoundViewArea) * scaleX); + offsetLeft = (left - viewportScrollX) * scaleX; + offsetRight = (right - viewportScrollX) * scaleX; + } else { + // freeze + // viewMainLeft may not start at col = 0 + // DO NOT use viewMainLeft?.viewBound.right. It's not accurate. there is a delay to set viewBound! + const leftToCanvas = left - (freezeStartX - rowHeaderWidth); + const rightToCanvas = right - (freezeStartX - rowHeaderWidth); + if (right < freezeEndX) { + offsetLeft = leftToCanvas * scaleX; + offsetRight = rightToCanvas * scaleX; + } else if (left <= freezeEndX && right >= freezeEndX) { + offsetLeft = leftToCanvas * scaleX; + offsetRight = Math.max(viewBoundsLeft, (right - viewportScrollX) * scaleX); + } else if (left > freezeEndX) { + absolute.left = false; + offsetLeft = Math.max((left - viewportScrollX) * scaleX, viewBoundsLeft); + offsetRight = Math.max((right - viewportScrollX) * scaleX, viewBoundsLeft); + } } - let offsetTop: number; - let offsetBottom: number; - // viewMain or viewTop - if (top < topBoundOfViewArea) { - absolute.top = true; - offsetTop = ((topBoundOfViewArea) + (top - topBoundOfViewArea)) * scaleY; - offsetBottom = Math.max( - Math.min( - ((topBoundOfViewArea) + (right - topBoundOfViewArea)) * scaleY, - (topBoundOfViewArea) * scaleY - ), - (bottom - viewportScrollY) * scaleY - ); - } else { + let offsetTop: number = 0; + let offsetBottom: number = 0; + if (freezedRow === 0) { absolute.top = false; - offsetTop = Math.max((top - viewportScrollY) * scaleY, (topBoundOfViewArea) * scaleY); - offsetBottom = Math.max((bottom - viewportScrollY) * scaleY, (topBoundOfViewArea) * scaleY); + offsetTop = (top - viewportScrollY) * scaleY; + offsetBottom = (bottom - viewportScrollY) * scaleY; + } else { + const topToCanvas = top - (freezeStartY - columnHeaderHeight); + const bottomToCanvas = bottom - (freezeStartY - columnHeaderHeight); + if (bottom < freezeEndY) { + offsetTop = topToCanvas * scaleY; + offsetBottom = bottomToCanvas * scaleY; + } else if (top <= freezeEndY && bottom >= freezeEndY) { + offsetTop = topToCanvas * scaleY; + offsetBottom = Math.max(viewBoundsTop, (bottom - viewportScrollY) * scaleY); + } else if (top > freezeEndY) { + absolute.top = false; + offsetTop = Math.max((top - viewportScrollY) * scaleY, viewBoundsTop); + offsetBottom = Math.max((bottom - viewportScrollY) * scaleY, viewBoundsTop); + } } - return { + offsetLeft = Math.max(offsetLeft, boundsOfViewArea.left); + offsetTop = Math.max(offsetTop, boundsOfViewArea.top); + offsetRight = Math.max(offsetRight, boundsOfViewArea.left); + offsetBottom = Math.max(offsetBottom, boundsOfViewArea.top); + + const rs = { left: offsetLeft, right: offsetRight, top: offsetTop, bottom: offsetBottom, absolute, }; + return rs; } /** @@ -728,6 +768,7 @@ export class SheetCanvasFloatDomManagerService extends Disposable { }; } + // eslint-disable-next-line max-lines-per-function, complexity addFloatDomToRange(range: IRange, config: ICanvasFloatDom, domAnchor: Partial, propId?: string) { const target = getSheetCommandTarget(this._univerInstanceService, { unitId: config.unitId, @@ -796,8 +837,8 @@ export class SheetCanvasFloatDomManagerService extends Disposable { if (!skMangerService) { return; } - const skeleton = skMangerService.getWorksheetSkeleton(subUnitId); - if (!skeleton) { + const skeletonParam = skMangerService.getWorksheetSkeleton(subUnitId); + if (!skeletonParam) { return; } @@ -870,9 +911,11 @@ export class SheetCanvasFloatDomManagerService extends Disposable { const disposableCollection = new DisposableCollection(); const viewMain = scene.getMainViewport(); + const { rowHeaderWidth, columnHeaderHeight } = skeletonParam.skeleton; + const boundsOfViewArea: IBoundRectNoAngle = { - top: viewMain.top, - left: viewMain.left, + top: columnHeaderHeight, + left: rowHeaderWidth, bottom: viewMain.bottom, right: viewMain.right, }; @@ -886,7 +929,7 @@ export class SheetCanvasFloatDomManagerService extends Disposable { subUnitId, } as unknown as ICanvasFloatDomInfo; - const initedPosition = calcPosition(domRect, renderObject.renderUnit, skeleton.skeleton, target.worksheet, floatDomInfo); + const initedPosition = calcPosition(domRect, renderObject.renderUnit, skeletonParam.skeleton, target.worksheet, floatDomInfo); const position$ = new BehaviorSubject(initedPosition); floatDomInfo.position$ = position$; @@ -943,7 +986,7 @@ export class SheetCanvasFloatDomManagerService extends Disposable { height: domAnchor.height ?? newRangePos.height, zIndex: this._drawingManagerService.getDrawingOrder(unitId, subUnitId).length - 1, }); - const newPos = calcPosition(newRect, renderObject.renderUnit, skeleton.skeleton, target.worksheet, floatDomInfo); + const newPos = calcPosition(newRect, renderObject.renderUnit, skeletonParam.skeleton, target.worksheet, floatDomInfo); position$.next(newPos); })); const skm = this._renderManagerService.getRenderById(unitId)?.with(SheetSkeletonManagerService); @@ -956,7 +999,7 @@ export class SheetCanvasFloatDomManagerService extends Disposable { }); const listener = domRect.onTransformChange$.subscribeEvent(() => { - const newPosition = calcPosition(domRect, renderObject.renderUnit, skeleton.skeleton, target.worksheet, floatDomInfo); + const newPosition = calcPosition(domRect, renderObject.renderUnit, skeletonParam.skeleton, target.worksheet, floatDomInfo); position$.next( newPosition ); diff --git a/packages/sheets-ui/src/controllers/format-painter/format-painter.controller.ts b/packages/sheets-ui/src/controllers/format-painter/format-painter.controller.ts index 34d00434aeb..a8ce5da6c82 100644 --- a/packages/sheets-ui/src/controllers/format-painter/format-painter.controller.ts +++ b/packages/sheets-ui/src/controllers/format-painter/format-painter.controller.ts @@ -59,7 +59,10 @@ export class FormatPainterController extends Disposable { } private _commandExecutedListener() { - const selectionRenderService = this._renderManagerService.getCurrentTypeOfRenderer(UniverInstanceType.UNIVER_SHEET)!.with(ISheetSelectionRenderService); + const unitId = this._univerInstanceService.getFocusedUnit()?.getUnitId() || ''; + const renderUnit = this._renderManagerService.getRenderById(unitId); + if (!renderUnit) return; + const selectionRenderService = renderUnit.with(ISheetSelectionRenderService); this.disposeWithMe( selectionRenderService.selectionMoveEnd$.subscribe((selections) => { diff --git a/packages/sheets-ui/src/controllers/render-controllers/freeze.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/freeze.render-controller.ts index 6e72d1fc888..b84bb40bfee 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/freeze.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/freeze.render-controller.ts @@ -884,6 +884,36 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderM right: 0, }); viewMain.resetPadding(); + viewMainLeft.resizeWhenFreezeChange({ + left: rowHeaderWidthAndMarginLeft, + top: columnHeaderHeightAndMarginTop, + bottom: 0, + width: 0, + }); + viewMainTop.resizeWhenFreezeChange({ + left: rowHeaderWidthAndMarginLeft, + top: columnHeaderHeightAndMarginTop, + height: 0, + right: 0, + }); + viewMainLeftTop.resizeWhenFreezeChange({ + left: rowHeaderWidthAndMarginLeft, + top: columnHeaderHeightAndMarginTop, + width: 0, + height: 0, + }); + viewRowTop.resizeWhenFreezeChange({ + left: 0, + top: columnHeaderHeightAndMarginTop, + width: rowHeaderWidthAndMarginLeft, + height: 0, + }); + viewColumnLeft.resizeWhenFreezeChange({ + left: 0, + top: 0, + height: columnHeaderHeightAndMarginTop, + width: 0, + }); } else if (isTopView === true && isLeftView === false) { // freeze row const topGap = endSheetView.startY - startSheetView.startY; diff --git a/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts index 6545fc43d0c..d566a818dba 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts @@ -413,6 +413,8 @@ export class SheetRenderController extends RxDisposable implements IRenderModule commandId, }, true); + // TODO @lumixraku + // This is insane !!! Tons changes would call sk.setCurrent. this._sheetSkeletonManagerService.setCurrent({ sheetId: worksheetId, commandId, diff --git a/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts b/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts index df0cc75c7c8..28ba3b57c06 100644 --- a/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts +++ b/packages/sheets-ui/src/services/sheet-skeleton-manager.service.ts @@ -44,7 +44,7 @@ export interface ISheetSkeletonManagerSearch { export class SheetSkeletonManagerService extends Disposable implements IRenderModule { private _sheetId: string = ''; - // @TODO lumixraku, why need this? How about put dirty & sheetId & unitId in skeleton itself z? + // @TODO lumixraku, why need this? How about put dirty & sheetId & unitId in skeleton itself? private _sheetSkeletonParamStore: Map = new Map(); private readonly _currentSkeleton$ = new BehaviorSubject>(null); @@ -68,6 +68,7 @@ export class SheetSkeletonManagerService extends Disposable implements IRenderMo this._currentSkeletonBefore$.complete(); this._currentSkeleton$.complete(); this._sheetSkeletonParamStore = new Map(); + this._sheetSkService.deleteSkeleton(this._context.unitId, this._sheetId); }); this._initRemoveSheet(); @@ -131,6 +132,10 @@ export class SheetSkeletonManagerService extends Disposable implements IRenderMo return param; } + /** + * Command in COMMAND_LISTENER_SKELETON_CHANGE would cause setCurrent, see @packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts + * @param searchParam + */ setCurrent(searchParam: ISheetSkeletonManagerSearch): Nullable { this._setCurrent(searchParam.sheetId); }