mirror of
https://github.com/dream-num/univer.git
synced 2026-09-19 10:29:12 +08:00
feat(render-engine): move cache to viewport (#2188)
* fix(sheet): render viewport wrong size * fix: new viewport need more param * feat: cache by viewport * fix: markdirty & scrolling was conflict before, so scrolling & dirty did not refresh whole viewport * fix: 0510-1725 fix: #676 #677 fix: browser native zoom * fix: blurry when applyCache especially in windows 175% * fix: merge cells background fix: #676 test: #677 fix: #700 fix: aux line background fix: spreadsheet fix: precise rect for background * feat: fix cf render error on printing mode fix: #700 * fix: viewport make dirty false chore: eslint fix * test(facade): fix canvas mock fix: new viewport need more param222 test: mock canvas fix: rm useless * fix: should beginPath before clip() * chore: comments to english fix: expand shouldupdatecache chore: better code * fix: change viewport update timing fix: update visible cache viewport area --------- Co-authored-by: DR-Univer <wbfsa@qq.com> Co-authored-by: zw <zhang9748@foxmail.com> Co-authored-by: 白熱 <sonne@asaki.me>
This commit is contained in:
@@ -283,4 +283,30 @@ export class Rectangle {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static hasIntersectionBetweenTwoBounds(
|
||||
rect1: {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
},
|
||||
rect2: {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
) {
|
||||
if (
|
||||
rect1.left > rect2.right || // rect1 在 rect2 右侧
|
||||
rect1.right < rect2.left || // rect1 在 rect2 左侧
|
||||
rect1.top > rect2.bottom || // rect1 在 rect2 下方
|
||||
rect1.bottom < rect2.top // rect1 在 rect2 上方
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,8 @@ export class DocCanvasView extends RxDisposable {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isRelativeX: true,
|
||||
isRelativeY: true,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import type { IObjectFullState, ITransformChangeState } from './basics/interface
|
||||
import { TRANSFORM_CHANGE_OBSERVABLE_TYPE } from './basics/interfaces';
|
||||
import { generateRandomKey, toPx } from './basics/tools';
|
||||
import { Transform } from './basics/transform';
|
||||
import type { IViewportBound, Vector2 } from './basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from './basics/vector2';
|
||||
import type { UniverRenderingContext } from './context';
|
||||
import type { Layer } from './layer';
|
||||
|
||||
@@ -87,6 +87,7 @@ export abstract class BaseObject extends Disposable {
|
||||
protected _oKey: string;
|
||||
|
||||
protected _dirty: boolean = true;
|
||||
protected _forceDirty: boolean = true;
|
||||
|
||||
private _top: number = 0;
|
||||
|
||||
@@ -367,6 +368,10 @@ export abstract class BaseObject extends Disposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
makeForceDirty(state: boolean = true) {
|
||||
this._forceDirty = state;
|
||||
}
|
||||
|
||||
makeDirtyNoDebounce(state: boolean = true) {
|
||||
this._dirty = state;
|
||||
if (state) {
|
||||
@@ -514,7 +519,7 @@ export abstract class BaseObject extends Disposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
isRender(bounds?: IViewportBound) {
|
||||
isRender(bounds?: IViewportInfo) {
|
||||
if (this._forceRender) {
|
||||
return false;
|
||||
}
|
||||
@@ -551,7 +556,7 @@ export abstract class BaseObject extends Disposable {
|
||||
this._makeDirtyMix();
|
||||
}
|
||||
|
||||
render(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
render(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
/* abstract */
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import type {
|
||||
LocaleService,
|
||||
Nullable,
|
||||
} from '@univerjs/core';
|
||||
import { BaselineOffset, DEFAULT_STYLES, FontStyleType, Tools } from '@univerjs/core';
|
||||
import { BaselineOffset, DEFAULT_STYLES, FontStyleType, Rectangle, Tools } from '@univerjs/core';
|
||||
import * as cjk from 'cjk-regex';
|
||||
|
||||
import { FontCache } from '../components/docs/layout/shaping-engine/font-cache';
|
||||
@@ -757,3 +757,79 @@ export function ptToPixel(pt: number) {
|
||||
export function pixelToPt(px: number) {
|
||||
return px * PX_TO_PT_RATIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前单元格在任意一个 viewRanges 中
|
||||
* @param ranges
|
||||
* @param rowIndex
|
||||
* @param colIndex
|
||||
* @returns
|
||||
*/
|
||||
export function inViewRanges(ranges: IRange[], rowIndex: number, colIndex: number) {
|
||||
for (const range of ranges) {
|
||||
if (rowIndex >= range.startRow && rowIndex <= range.endRow &&
|
||||
colIndex >= range.startColumn && colIndex <= range.endColumn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在非下方区域中
|
||||
* @param ranges
|
||||
* @param rowIndex
|
||||
* @returns
|
||||
*/
|
||||
export function inCurrentAndAboveViewRanges(ranges: IRange[], rowIndex: number) {
|
||||
for (const range of ranges) {
|
||||
if (rowIndex > range.endRow) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* row 在任意一个 Range 中
|
||||
* @param ranges
|
||||
* @param rowIndex
|
||||
* @returns
|
||||
*/
|
||||
export function inRowViewRanges(ranges: IRange[], rowIndex: number) {
|
||||
let flag = false;
|
||||
for (const range of ranges) {
|
||||
if (rowIndex >= range.startRow && rowIndex <= range.endRow) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 range 有相交, 那么扩展到第一组 range 中.
|
||||
* @param ranges
|
||||
*/
|
||||
export function mergeRangeIfIntersects(mainRanges: IRange[], ranges: IRange[]) {
|
||||
for (const mainRange of mainRanges) {
|
||||
for (const range of ranges) {
|
||||
if (Rectangle.intersects(mainRange, range)) {
|
||||
mainRange.startRow = Math.min(mainRange.startRow, range.startRow);
|
||||
mainRange.endRow = Math.max(mainRange.endRow, range.endRow);
|
||||
mainRange.startColumn = Math.min(mainRange.startColumn, range.startColumn);
|
||||
mainRange.endColumn = Math.max(mainRange.endColumn, range.endColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
return mainRanges;
|
||||
}
|
||||
|
||||
export function clampRanges(range: IRange) {
|
||||
return {
|
||||
startRow: Math.max(0, range.startRow),
|
||||
startColumn: Math.max(0, range.startColumn),
|
||||
endRow: Math.max(0, range.endRow),
|
||||
endColumn: Math.max(0, range.endColumn),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,6 +111,8 @@ export class Transform {
|
||||
this._m[1] *= sx;
|
||||
this._m[2] *= sy;
|
||||
this._m[3] *= sy;
|
||||
this._m[4] *= sx;
|
||||
this._m[5] *= sy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -387,6 +389,18 @@ export class Transform {
|
||||
return scaleMatrix;
|
||||
}
|
||||
|
||||
convert2DOMMatrix2D() {
|
||||
const m = this.getMatrix();
|
||||
return {
|
||||
a: m[0],
|
||||
b: m[1],
|
||||
c: m[2],
|
||||
d: m[3],
|
||||
e: m[4],
|
||||
f: m[5],
|
||||
};
|
||||
}
|
||||
|
||||
// static createTransformByState(state: positionState) {
|
||||
// const newTr = new this();
|
||||
// let { x, y, angle, scaleX, scaleY, skewX, skewY, flipX, flipY } = state;
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Canvas } from '../canvas';
|
||||
import type { SHEET_VIEWPORT_KEY } from '../components/sheets/interfaces';
|
||||
import type { DeepImmutable, FloatArray } from './i-events';
|
||||
import type { Transform } from './transform';
|
||||
|
||||
export interface IPoint {
|
||||
x: number;
|
||||
@@ -844,17 +847,64 @@ export interface IBoundRect {
|
||||
}
|
||||
|
||||
export interface IBoundRectNoAngle {
|
||||
/**
|
||||
* 冻结区域相对 MainCanvas 的物理位置,
|
||||
* left = n * colWidth + rowHeaderWidth
|
||||
*/
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export interface IViewportBound {
|
||||
export interface IViewportInfo {
|
||||
viewBound: IBoundRectNoAngle;
|
||||
diffBounds: IBoundRectNoAngle[];
|
||||
|
||||
/**
|
||||
* scroll right further diffX < 0
|
||||
* previewBound.x - viewbound.x
|
||||
*/
|
||||
diffX: number;
|
||||
diffY: number;
|
||||
|
||||
/**
|
||||
* The physical position of the frozen rows and columns on the canvas, used for drawImage.
|
||||
* For example, if the freezing starts from the fourth column, the left position would be 4 * column + rowHeaderWidth.
|
||||
* The physical position means the top and left values have already considered the scaling factor.
|
||||
*/
|
||||
viewPortPosition: IBoundRectNoAngle;
|
||||
viewPortKey?: string;
|
||||
viewportKey: string | SHEET_VIEWPORT_KEY;
|
||||
/**
|
||||
* In the future, a number will be used to indicate the reason for the "dirty" status
|
||||
* Here, a binary value is used to facilitate computation.
|
||||
*/
|
||||
isDirty?: number;
|
||||
isForceDirty?: boolean;
|
||||
|
||||
allowCache?: boolean;
|
||||
cacheBound: IBoundRectNoAngle;
|
||||
diffCacheBounds: IBoundRectNoAngle[];
|
||||
cacheViewPortPosition: IBoundRectNoAngle;
|
||||
|
||||
shouldCacheUpdate: number;
|
||||
sceneTrans: Transform;
|
||||
cacheCanvas?: Canvas;
|
||||
|
||||
leftOrigin: number;
|
||||
topOrigin: number;
|
||||
|
||||
bufferEdgeX: number;
|
||||
bufferEdgeY: number;
|
||||
|
||||
updatePrevCacheBounds?: (viewbound: IBoundRectNoAngle) => void;
|
||||
}
|
||||
|
||||
export interface IViewportInfos {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
@@ -130,21 +130,18 @@ export class Canvas {
|
||||
// this.setWidth(width || 0);
|
||||
// this.setHeight(height || 0);
|
||||
this._pixelRatio = pixelRatioParam || getDevicePixelRatio();
|
||||
const canvsElement = this.getCanvasEle();
|
||||
|
||||
if (width) {
|
||||
this.getCanvasEle().width = width * this._pixelRatio;
|
||||
|
||||
this._width = this.getCanvasEle().width / this._pixelRatio;
|
||||
|
||||
this.getCanvasEle().style.width = `${this._width}px`;
|
||||
if (canvsElement && width !== undefined) {
|
||||
canvsElement.width = width * this._pixelRatio;
|
||||
this._width = canvsElement.width / this._pixelRatio;
|
||||
canvsElement.style.width = `${this._width}px`;
|
||||
}
|
||||
|
||||
if (height) {
|
||||
this.getCanvasEle().height = height * this._pixelRatio;
|
||||
|
||||
this._height = this.getCanvasEle().height / this._pixelRatio;
|
||||
|
||||
this.getCanvasEle().style.height = `${this._height}px`;
|
||||
if (canvsElement && height !== undefined) {
|
||||
canvsElement.height = height * this._pixelRatio;
|
||||
this._height = canvsElement.height / this._pixelRatio;
|
||||
canvsElement.style.height = `${this._height}px`;
|
||||
}
|
||||
|
||||
this.getContext().setTransform(this._pixelRatio, 0, 0, this._pixelRatio, 0, 0);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { DisposableCollection, sortRules, toDisposable } from '@univerjs/core';
|
||||
import type { IDisposable } from '@wendellhu/redi';
|
||||
import { BaseObject } from '../base-object';
|
||||
import type { IViewportBound } from '../basics/vector2';
|
||||
import type { IViewportInfo } from '../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../context';
|
||||
import type { ComponentExtension } from './extension';
|
||||
|
||||
@@ -58,7 +58,7 @@ export class RenderComponent<T, U, V> extends BaseObject {
|
||||
return this._extensions.get(uKey);
|
||||
}
|
||||
|
||||
draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
/* abstract */
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IViewportBound } from '../../basics/vector2';
|
||||
import type { IViewportInfo } from '../../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
import { Rect } from '../../shape';
|
||||
import { Liquid } from './liquid';
|
||||
@@ -40,7 +40,7 @@ export class DocBackground extends DocComponent {
|
||||
return new DocBackground(oKey, documentSkeleton, config);
|
||||
}
|
||||
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
const skeletonData = this.getSkeleton()?.getSkeletonData();
|
||||
|
||||
if (skeletonData == null) {
|
||||
@@ -103,7 +103,7 @@ export class DocBackground extends DocComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
this.draw(ctx, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { RENDER_CLASS_TYPE } from '../../basics/const';
|
||||
import type { IDocumentSkeletonGlyph, IDocumentSkeletonLine, IDocumentSkeletonPage } from '../../basics/i-document-skeleton-cached';
|
||||
import { PageLayoutType } from '../../basics/i-document-skeleton-cached';
|
||||
import type { IBoundRectNoAngle, IViewportBound } from '../../basics/vector2';
|
||||
import type { IBoundRectNoAngle, IViewportInfo } from '../../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
import { RenderComponent } from '../component';
|
||||
import type { DOCS_EXTENSION_TYPE } from './doc-extension';
|
||||
@@ -82,7 +82,7 @@ export abstract class DocComponent extends RenderComponent<
|
||||
}
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
if (!this.visible) {
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
@@ -118,7 +118,7 @@ export abstract class DocComponent extends RenderComponent<
|
||||
};
|
||||
}
|
||||
|
||||
isSkipByDiffBounds(page: IDocumentSkeletonPage, pageTop: number, pageLeft: number, bounds?: IViewportBound) {
|
||||
isSkipByDiffBounds(page: IDocumentSkeletonPage, pageTop: number, pageLeft: number, bounds?: IViewportInfo) {
|
||||
if (bounds === null || bounds === undefined) {
|
||||
return false;
|
||||
}
|
||||
@@ -146,5 +146,5 @@ export abstract class DocComponent extends RenderComponent<
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract _draw(ctx: UniverRenderingContext, bounds?: IViewportBound): void;
|
||||
protected abstract _draw(ctx: UniverRenderingContext, bounds?: IViewportInfo): void;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import type { IDocumentSkeletonCached, IDocumentSkeletonPage } from '../../basic
|
||||
import { LineType } from '../../basics/i-document-skeleton-cached';
|
||||
import { degToRad } from '../../basics/tools';
|
||||
import type { Transform } from '../../basics/transform';
|
||||
import type { IViewportBound } from '../../basics/vector2';
|
||||
import type { IViewportInfo } from '../../basics/vector2';
|
||||
import { Vector2 } from '../../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
import type { Scene } from '../../scene';
|
||||
@@ -164,7 +164,7 @@ export class Documents extends DocComponent {
|
||||
return (this.getScene() as Scene).getEngine();
|
||||
}
|
||||
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
const skeletonData = this.getSkeleton()?.getSkeletonData();
|
||||
|
||||
if (skeletonData == null) {
|
||||
@@ -466,7 +466,7 @@ export class Documents extends DocComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
this.draw(ctx, bounds);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import type {
|
||||
} from '../../../basics/i-document-skeleton-cached';
|
||||
import { GlyphType, LineType, PageLayoutType } from '../../../basics/i-document-skeleton-cached';
|
||||
import type { IDocsConfig, INodeInfo, INodePosition, INodeSearch } from '../../../basics/interfaces';
|
||||
import type { IViewportBound, Vector2 } from '../../../basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from '../../../basics/vector2';
|
||||
import { Skeleton } from '../../skeleton';
|
||||
import { Liquid } from '../liquid';
|
||||
import type { DocumentViewModel } from '../view-model/document-view-model';
|
||||
@@ -94,7 +94,7 @@ export class DocumentSkeleton extends Skeleton {
|
||||
}
|
||||
|
||||
// Layout the document.
|
||||
calculate(bounds?: IViewportBound) {
|
||||
calculate(bounds?: IViewportInfo) {
|
||||
if (!this.dirty) {
|
||||
return;
|
||||
}
|
||||
@@ -555,7 +555,7 @@ export class DocumentSkeleton extends Skeleton {
|
||||
* @returns view model: skeleton
|
||||
*/
|
||||
|
||||
private _createSkeleton(ctx: ILayoutContext, _bounds?: IViewportBound): IDocumentSkeletonCached {
|
||||
private _createSkeleton(ctx: ILayoutContext, _bounds?: IViewportInfo): IDocumentSkeletonCached {
|
||||
// console.log('createSkeleton: iterate ', this._iteratorCount, 'times');
|
||||
const { viewModel, skeleton, skeletonResourceReference } = ctx;
|
||||
|
||||
|
||||
+1
-1
@@ -854,7 +854,7 @@ export class TextSelectionRenderManager extends RxDisposable implements ITextSel
|
||||
return;
|
||||
}
|
||||
const unitId = this._docSkeleton.getViewModel().getDataModel().getUnitId();
|
||||
const key = `${unitId}_${viewport.viewPortKey}`;
|
||||
const key = `${unitId}_${viewport.viewportKey}`;
|
||||
|
||||
if (this._viewPortObserverMap.has(key)) {
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDocumentRenderConfig, IScale, Nullable } from '@univerjs/core';
|
||||
import type { IDocumentRenderConfig, IRange, IScale, Nullable } from '@univerjs/core';
|
||||
import { Registry } from '@univerjs/core';
|
||||
|
||||
import type { BaseObject } from '../base-object';
|
||||
@@ -31,6 +31,11 @@ export interface IExtensionConfig {
|
||||
renderConfig?: IDocumentRenderConfig;
|
||||
}
|
||||
|
||||
export interface IDrawInfo {
|
||||
viewRanges: IRange[];
|
||||
viewportKey: string;
|
||||
checkOutOfViewBound?: boolean;
|
||||
}
|
||||
export class ComponentExtension<T, U, V> {
|
||||
uKey: string = '';
|
||||
|
||||
@@ -50,7 +55,7 @@ export class ComponentExtension<T, U, V> {
|
||||
return this.Z_INDEX;
|
||||
}
|
||||
|
||||
draw(ctx: UniverRenderingContext, parentScale: IScale, skeleton: T, diffBounds?: V) {
|
||||
draw(ctx: UniverRenderingContext, parentScale: IScale, skeleton: T, diffBounds?: V, more?: IDrawInfo) {
|
||||
/* abstract */
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import type { Nullable } from '@univerjs/core';
|
||||
import type { IViewportBound, Vector2 } from '../../basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from '../../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
import { SheetColumnHeaderExtensionRegistry } from '../extension';
|
||||
import type { ColumnHeaderLayout } from './extensions/column-header-layout';
|
||||
@@ -49,7 +49,7 @@ export class SpreadsheetColumnHeader extends SpreadsheetHeader {
|
||||
return this._columnHeaderLayoutExtension;
|
||||
}
|
||||
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
const spreadsheetSkeleton = this.getSkeleton();
|
||||
if (!spreadsheetSkeleton) {
|
||||
return;
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
import type { IRange, IScale } from '@univerjs/core';
|
||||
|
||||
import { getColor } from '../../../basics/tools';
|
||||
import { fixLineWidthByScale, getColor, inViewRanges, mergeRangeIfIntersects } from '../../../basics/tools';
|
||||
import type { UniverRenderingContext } from '../../../context';
|
||||
import type { IDrawInfo } from '../../extension';
|
||||
import { SpreadsheetExtensionRegistry } from '../../extension';
|
||||
import type { SpreadsheetSkeleton } from '../sheet-skeleton';
|
||||
import type { Spreadsheet } from '../spreadsheet';
|
||||
@@ -25,8 +26,12 @@ import { SheetExtension } from './sheet-extension';
|
||||
|
||||
const UNIQUE_KEY = 'DefaultBackgroundExtension';
|
||||
|
||||
const DOC_EXTENSION_Z_INDEX = 40;
|
||||
const PRINTING_Z_INDEX = 20;
|
||||
/**
|
||||
* in prev version background ext is higer than font ext. now turing back lower than font ext.
|
||||
* font ext zindex is 30.
|
||||
*/
|
||||
const DOC_EXTENSION_Z_INDEX = 21;
|
||||
const PRINTING_Z_INDEX = 21;
|
||||
|
||||
export class Background extends SheetExtension {
|
||||
override uKey = UNIQUE_KEY;
|
||||
@@ -43,7 +48,8 @@ export class Background extends SheetExtension {
|
||||
ctx: UniverRenderingContext,
|
||||
parentScale: IScale,
|
||||
spreadsheetSkeleton: SpreadsheetSkeleton,
|
||||
diffRanges?: IRange[]
|
||||
diffRanges: IRange[],
|
||||
{ viewRanges, checkOutOfViewBound }: IDrawInfo
|
||||
) {
|
||||
const { stylesCache } = spreadsheetSkeleton;
|
||||
const { background, backgroundPositions } = stylesCache;
|
||||
@@ -63,61 +69,54 @@ export class Background extends SheetExtension {
|
||||
return;
|
||||
}
|
||||
ctx.save();
|
||||
|
||||
ctx.setGlobalCompositeOperation('destination-over');
|
||||
const { scaleX, scaleY } = ctx.getScale();
|
||||
background &&
|
||||
Object.keys(background).forEach((rgb: string) => {
|
||||
const backgroundCache = background[rgb];
|
||||
|
||||
ctx.fillStyle = rgb || getColor([255, 255, 255])!;
|
||||
ctx.beginPath();
|
||||
backgroundCache.forValue((rowIndex, columnIndex) => {
|
||||
const cellInfo = backgroundPositions?.getValue(rowIndex, columnIndex);
|
||||
|
||||
const backgroundPaths = new Path2D();
|
||||
backgroundCache.forValue((rowIndex, columnIndex) => {
|
||||
if (!checkOutOfViewBound && !inViewRanges(viewRanges, rowIndex, columnIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const cellInfo = backgroundPositions?.getValue(rowIndex, columnIndex);
|
||||
if (cellInfo == null) {
|
||||
return true;
|
||||
}
|
||||
let { startY, endY, startX, endX } = cellInfo;
|
||||
const { isMerged, isMergedMainCell, mergeInfo } = cellInfo;
|
||||
const mergeTo = diffRanges && diffRanges.length > 0 ? diffRanges : viewRanges;
|
||||
const combineWithMergeRanges = mergeRangeIfIntersects(mergeTo, [mergeInfo]);
|
||||
|
||||
// If curr cell is not in the viewrange (viewport + merged cells), exit early.
|
||||
if (!inViewRanges(combineWithMergeRanges!, rowIndex, columnIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For merged cells && cells that are not top-left,
|
||||
// we need to use the background color of the top-left cell.
|
||||
if (isMerged) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.isRenderDiffRangesByCell(
|
||||
{
|
||||
startRow: mergeInfo.startRow,
|
||||
endRow: mergeInfo.endRow,
|
||||
startColumn: mergeInfo.startColumn,
|
||||
endColumn: mergeInfo.endColumn,
|
||||
},
|
||||
diffRanges
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if (
|
||||
// !this.isRenderDiffRangesByColumn(mergeInfo.startColumn, diffRanges) &&
|
||||
// !this.isRenderDiffRangesByColumn(mergeInfo.endColumn, diffRanges)
|
||||
// ) {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// For merged cells, and the current cell is the top-left cell in the merged region.
|
||||
if (isMergedMainCell) {
|
||||
startY = mergeInfo.startY;
|
||||
endY = mergeInfo.endY;
|
||||
startX = mergeInfo.startX;
|
||||
endX = mergeInfo.endX;
|
||||
}
|
||||
|
||||
ctx.moveToByPrecision(startX, startY);
|
||||
ctx.lineToByPrecision(startX, endY);
|
||||
ctx.lineToByPrecision(endX, endY);
|
||||
ctx.lineToByPrecision(endX, startY);
|
||||
// precise is a workaround for windows, macOS does not have this issue.
|
||||
const startXPrecise = fixLineWidthByScale(startX, scaleX);
|
||||
const startYPrecise = fixLineWidthByScale(startY, scaleY);
|
||||
const endXPrecise = fixLineWidthByScale(endX, scaleX);
|
||||
const endYPrecise = fixLineWidthByScale(endY, scaleY);
|
||||
backgroundPaths.rect(startXPrecise, startYPrecise, endXPrecise - startXPrecise, endYPrecise - startYPrecise);
|
||||
});
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.fill(backgroundPaths);
|
||||
});
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export class Border extends SheetExtension {
|
||||
|
||||
override Z_INDEX = BORDER_Z_INDEX;
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
override draw(
|
||||
ctx: UniverRenderingContext,
|
||||
parentScale: IScale,
|
||||
@@ -66,6 +67,7 @@ export class Border extends SheetExtension {
|
||||
|
||||
const precisionScale = this._getScale(ctx.getScale());
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
border?.forValue((rowIndex, columnIndex, borderCaches) => {
|
||||
if (!borderCaches) {
|
||||
return true;
|
||||
|
||||
@@ -19,19 +19,21 @@
|
||||
import type { ICellData, IRange, IScale, ObjectMatrix } from '@univerjs/core';
|
||||
import { HorizontalAlign, WrapStrategy } from '@univerjs/core';
|
||||
|
||||
import { VERTICAL_ROTATE_ANGLE } from '../../../basics/text-rotation';
|
||||
import { inRowViewRanges, inViewRanges, mergeRangeIfIntersects } from '../../../basics/tools';
|
||||
import type { UniverRenderingContext } from '../../../context';
|
||||
import type { Documents } from '../../docs/document';
|
||||
import type { IDrawInfo } from '../../extension';
|
||||
import { SpreadsheetExtensionRegistry } from '../../extension';
|
||||
import type { IFontCacheItem } from '../interfaces';
|
||||
import type { SheetComponent } from '../sheet-component';
|
||||
import { getDocsSkeletonPageSize, type SpreadsheetSkeleton } from '../sheet-skeleton';
|
||||
import { VERTICAL_ROTATE_ANGLE } from '../../../basics/text-rotation';
|
||||
import { FIX_ONE_PIXEL_BLUR_OFFSET } from '../../../basics';
|
||||
import { SheetExtension } from './sheet-extension';
|
||||
|
||||
const UNIQUE_KEY = 'DefaultFontExtension';
|
||||
|
||||
const EXTENSION_Z_INDEX = 30;
|
||||
const EXTENSION_Z_INDEX = 45;
|
||||
export interface ISheetFontRenderExtension {
|
||||
fontRenderExtension?: {
|
||||
leftOffset?: number;
|
||||
@@ -55,8 +57,10 @@ export class Font extends SheetExtension {
|
||||
ctx: UniverRenderingContext,
|
||||
parentScale: IScale,
|
||||
spreadsheetSkeleton: SpreadsheetSkeleton,
|
||||
diffRanges?: IRange[]
|
||||
diffRanges: IRange[],
|
||||
moreBoundsInfo: IDrawInfo
|
||||
) {
|
||||
const { viewRanges = [], checkOutOfViewBound } = moreBoundsInfo;
|
||||
const { stylesCache, dataMergeCache, overflowCache, worksheet } = spreadsheetSkeleton;
|
||||
const { font: fontList } = stylesCache;
|
||||
if (!spreadsheetSkeleton || !worksheet) {
|
||||
@@ -76,15 +80,27 @@ export class Font extends SheetExtension {
|
||||
return;
|
||||
}
|
||||
ctx.save();
|
||||
|
||||
const scale = this._getScale(parentScale);
|
||||
|
||||
fontList &&
|
||||
Object.keys(fontList).forEach((fontFormat: string) => {
|
||||
const fontObjectArray = fontList[fontFormat];
|
||||
|
||||
// Since the overflow can spill out to both the left and right sides,
|
||||
// we need to consider the content outside the viewBounds.
|
||||
// At the same time, there are also merged cells, so we need to merge the current
|
||||
// viewrange and a single merged area when calculating.
|
||||
|
||||
// Early exit from font condition
|
||||
// If it's not an overflow and not within the field of view, we can exit early
|
||||
// (the field of view needs to consider the impact of merged cells).
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
fontObjectArray.forValue((rowIndex, columnIndex, docsConfig) => {
|
||||
if (!checkOutOfViewBound) {
|
||||
if (!inViewRanges(viewRanges!, rowIndex, columnIndex)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const cellInfo = this.getCellIndex(
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
@@ -99,18 +115,33 @@ export class Font extends SheetExtension {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the merged cell area intersects with the current viewRange,
|
||||
// then merge it into the current viewRange.
|
||||
// After the merge, the font extension within the current viewBounds
|
||||
// also needs to be drawn once.
|
||||
// But at this moment, we cannot assume that it is not within the viewRanges and exit, because there may still be horizontal overflow.
|
||||
// At this moment, we can only exclude the cells that are not within the current row.
|
||||
const mergeTo = diffRanges && diffRanges.length > 0 ? diffRanges : viewRanges;
|
||||
const combineWithMergeRanges = mergeRangeIfIntersects(mergeTo, [mergeInfo]);
|
||||
if (!inRowViewRanges(combineWithMergeRanges, rowIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isMergedMainCell) {
|
||||
startY = mergeInfo.startY;
|
||||
endY = mergeInfo.endY;
|
||||
startX = mergeInfo.startX;
|
||||
endX = mergeInfo.endX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incremental content rendering for texture mapping
|
||||
* startRow endRow 和 diffRanges 在 row 上不相交, 那么返回不渲染
|
||||
* PS 如果这个单元格并不在 merge 区域内, mergeInfo start 和 end 就是单元格本身
|
||||
*/
|
||||
if (!this.isRenderDiffRangesByRow(mergeInfo.startRow, mergeInfo.endRow, diffRanges)) {
|
||||
return true;
|
||||
if (diffRanges) {
|
||||
if (!this.isRowInRanges(mergeInfo.startRow, mergeInfo.endRow, diffRanges)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,9 +159,18 @@ export class Font extends SheetExtension {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the cell is overflowing, but the overflowRectangle has not been set,
|
||||
// then overflowRectangle is set to undefined.
|
||||
const overflowRectangle = overflowCache.getValue(rowIndex, columnIndex);
|
||||
const { horizontalAlign, vertexAngle = 0, centerAngle = 0 } = docsConfig;
|
||||
|
||||
// If it's neither an overflow nor within the current range,
|
||||
// then we can exit early (taking into account the range extension
|
||||
// caused by the merged cells).
|
||||
if (!overflowRectangle && !inViewRanges(combineWithMergeRanges, rowIndex, columnIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/dream-num/univer-pro/issues/334
|
||||
* When horizontal alignment is not set, the default alignment for rotation angles varies to accommodate overflow scenarios.
|
||||
@@ -176,15 +216,15 @@ export class Font extends SheetExtension {
|
||||
cellHeight - 2 / scale
|
||||
);
|
||||
ctx.clip();
|
||||
ctx.clearRectForTexture(
|
||||
startX + 1 / scale,
|
||||
startY + 1 / scale,
|
||||
cellWidth - 2 / scale,
|
||||
cellHeight - 2 / scale
|
||||
);
|
||||
// ctx.clearRectForTexture(
|
||||
// startX + 1 / scale,
|
||||
// startY + 1 / scale,
|
||||
// cellWidth - 2 / scale,
|
||||
// cellHeight - 2 / scale
|
||||
// );
|
||||
} else {
|
||||
if (horizontalAlignOverFlow === HorizontalAlign.CENTER) {
|
||||
this._clipRectangle(
|
||||
this._clipRectangleForOverflow(
|
||||
ctx,
|
||||
startRow,
|
||||
endRow,
|
||||
@@ -195,7 +235,7 @@ export class Font extends SheetExtension {
|
||||
columnWidthAccumulation
|
||||
);
|
||||
} else if (horizontalAlignOverFlow === HorizontalAlign.RIGHT) {
|
||||
this._clipRectangle(
|
||||
this._clipRectangleForOverflow(
|
||||
ctx,
|
||||
startRow,
|
||||
rowIndex,
|
||||
@@ -206,7 +246,7 @@ export class Font extends SheetExtension {
|
||||
columnWidthAccumulation
|
||||
);
|
||||
} else {
|
||||
this._clipRectangle(
|
||||
this._clipRectangleForOverflow(
|
||||
ctx,
|
||||
rowIndex,
|
||||
endRow,
|
||||
@@ -220,17 +260,18 @@ export class Font extends SheetExtension {
|
||||
}
|
||||
} else {
|
||||
ctx.rectByPrecision(startX + 1 / scale, startY + 1 / scale, cellWidth - 2 / scale, cellHeight - 2 / scale);
|
||||
// for normal cell, forbid text overflow cellarea
|
||||
ctx.clip();
|
||||
ctx.clearRectForTexture(
|
||||
startX + 1 / scale,
|
||||
startY + 1 / scale,
|
||||
cellWidth - 2 / scale,
|
||||
cellHeight - 2 / scale
|
||||
);
|
||||
// ctx.clearRectForTexture(
|
||||
// startX + 1 / scale,
|
||||
// startY + 1 / scale,
|
||||
// cellWidth - 2 / scale,
|
||||
// cellHeight - 2 / scale
|
||||
// );
|
||||
}
|
||||
|
||||
ctx.translate(startX + FIX_ONE_PIXEL_BLUR_OFFSET, startY + FIX_ONE_PIXEL_BLUR_OFFSET);
|
||||
this._renderDocuments(ctx, docsConfig, startX, startY, endX, endY, rowIndex, columnIndex, overflowCache);
|
||||
ctx.closePath();
|
||||
ctx.restore();
|
||||
});
|
||||
});
|
||||
@@ -291,7 +332,7 @@ export class Font extends SheetExtension {
|
||||
documents.changeSkeleton(documentSkeleton).render(ctx);
|
||||
}
|
||||
|
||||
private _clipRectangle(
|
||||
private _clipRectangleForOverflow(
|
||||
ctx: UniverRenderingContext,
|
||||
startRow: number,
|
||||
endRow: number,
|
||||
@@ -309,7 +350,7 @@ export class Font extends SheetExtension {
|
||||
|
||||
ctx.rectByPrecision(startX, startY, endX - startX, endY - startY);
|
||||
ctx.clip();
|
||||
ctx.clearRectForTexture(startX, startY, endX - startX, endY - startY);
|
||||
// ctx.clearRectForTexture(startX, startY, endX - startX, endY - startY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,7 @@ export class Marker extends SheetExtension {
|
||||
|
||||
override uKey: string = UNIQUE_KEY;
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
override draw(ctx: UniverRenderingContext, parentScale: IScale, skeleton: SpreadsheetSkeleton, diffRanges?: IRange[] | undefined): void {
|
||||
override draw(ctx: UniverRenderingContext, parentScale: IScale, skeleton: SpreadsheetSkeleton, diffRanges: IRange[]): void {
|
||||
const { worksheet, rowColumnSegment } = skeleton;
|
||||
if (!worksheet) {
|
||||
return;
|
||||
@@ -43,7 +42,6 @@ export class Marker extends SheetExtension {
|
||||
|
||||
const mergeCellRendered = new Set<string>();
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
Range.foreach(rowColumnSegment, (row, col) => {
|
||||
let cellData = worksheet.getCell(row, col);
|
||||
const cellInfo = this.getCellIndex(
|
||||
|
||||
@@ -37,14 +37,14 @@ export class SheetExtension extends ComponentExtension<SpreadsheetSkeleton, SHEE
|
||||
return getCellByIndex(rowIndex, columnIndex, rowHeightAccumulation, columnWidthAccumulation, dataMergeCache);
|
||||
}
|
||||
|
||||
isRenderDiffRangesByCell(range: IRange, diffRanges?: IRange[]) {
|
||||
isRenderDiffRangesByCell(rangeP: IRange, diffRanges?: IRange[]) {
|
||||
if (diffRanges == null || diffRanges.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const range of diffRanges) {
|
||||
const { startRow, startColumn, endRow, endColumn } = range;
|
||||
const isIntersect = Rectangle.intersects(range, {
|
||||
const isIntersect = Rectangle.intersects(rangeP, {
|
||||
startRow,
|
||||
endRow,
|
||||
startColumn,
|
||||
@@ -139,4 +139,47 @@ export class SheetExtension extends ComponentExtension<SpreadsheetSkeleton, SHEE
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 传入的 row 范围和 diffRanges 有相交, 返回 true
|
||||
* @param curStartRow
|
||||
* @param curEndRow
|
||||
* @param viewranges
|
||||
*/
|
||||
isRowInRanges(curStartRow: number, curEndRow: number, viewranges?: IRange[]) {
|
||||
if (viewranges == null || viewranges.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const range of viewranges) {
|
||||
const { startRow, endRow } = range;
|
||||
if (curStartRow >= startRow && curStartRow <= endRow) {
|
||||
return true;
|
||||
}
|
||||
if (curEndRow >= startRow && curEndRow <= endRow) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isIntersect = Rectangle.intersects(
|
||||
{
|
||||
startRow: curStartRow,
|
||||
endRow: curEndRow,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
},
|
||||
{
|
||||
startRow,
|
||||
endRow,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
}
|
||||
);
|
||||
|
||||
if (isIntersect) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import type {
|
||||
|
||||
import type { BORDER_TYPE } from '../../basics/const';
|
||||
import type { DocumentSkeleton } from '../docs/layout/doc-skeleton';
|
||||
import type { Canvas } from '../../canvas';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
|
||||
export interface BorderCache {
|
||||
[key: string]: BorderCacheItem | {};
|
||||
@@ -69,3 +71,39 @@ export enum ShowGridlinesState {
|
||||
OFF,
|
||||
ON,
|
||||
}
|
||||
|
||||
export enum SHEET_VIEWPORT_KEY {
|
||||
VIEW_MAIN = 'viewMain',
|
||||
VIEW_MAIN_LEFT_TOP = 'viewMainLeftTop',
|
||||
VIEW_MAIN_TOP = 'viewMainTop',
|
||||
VIEW_MAIN_LEFT = 'viewMainLeft',
|
||||
|
||||
VIEW_ROW_TOP = 'viewRowTop',
|
||||
VIEW_ROW_BOTTOM = 'viewRowBottom',
|
||||
VIEW_COLUMN_LEFT = 'viewColumnLeft',
|
||||
VIEW_COLUMN_RIGHT = 'viewColumnRight',
|
||||
VIEW_LEFT_TOP = 'viewLeftTop',
|
||||
}
|
||||
|
||||
export interface IPaintForRefresh {
|
||||
cacheCanvas: Canvas;
|
||||
cacheCtx: UniverRenderingContext;
|
||||
mainCtx: UniverRenderingContext;
|
||||
topOrigin: number;
|
||||
leftOrigin: number;
|
||||
bufferEdgeX: number;
|
||||
bufferEdgeY: number;
|
||||
}
|
||||
export interface IPaintForScrolling {
|
||||
cacheCanvas: Canvas;
|
||||
cacheCtx: UniverRenderingContext;
|
||||
mainCtx: UniverRenderingContext;
|
||||
topOrigin: number;
|
||||
leftOrigin: number;
|
||||
bufferEdgeX: number;
|
||||
bufferEdgeY: number;
|
||||
rowHeaderWidth: number;
|
||||
columnHeaderHeight: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import type { Nullable } from '@univerjs/core';
|
||||
import type { IViewportBound, Vector2 } from '../../basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from '../../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
import { SheetRowHeaderExtensionRegistry } from '../extension';
|
||||
import type { RowHeaderLayout } from './extensions/row-header-layout';
|
||||
@@ -50,7 +50,7 @@ export class SpreadsheetRowHeader extends SpreadsheetHeader {
|
||||
return this._rowHeaderLayoutExtension;
|
||||
}
|
||||
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
const spreadsheetSkeleton = this.getSkeleton();
|
||||
if (!spreadsheetSkeleton) {
|
||||
return;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import type { IRange, Nullable } from '@univerjs/core';
|
||||
|
||||
import { RENDER_CLASS_TYPE } from '../../basics/const';
|
||||
import type { IViewportBound, Vector2 } from '../../basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from '../../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
import { RenderComponent } from '../component';
|
||||
import type { SHEET_EXTENSION_TYPE } from './extensions/sheet-extension';
|
||||
@@ -39,7 +39,7 @@ export abstract class SheetComponent extends RenderComponent<SpreadsheetSkeleton
|
||||
this._skeleton = spreadsheetSkeleton;
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
if (!this.visible) {
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
@@ -82,7 +82,7 @@ export abstract class SheetComponent extends RenderComponent<SpreadsheetSkeleton
|
||||
endColumn: number
|
||||
): Nullable<{ startRow: number; startColumn: number; endRow: number; endColumn: number }>;
|
||||
|
||||
protected abstract _draw(ctx: UniverRenderingContext, bounds?: IViewportBound): void;
|
||||
protected abstract _draw(ctx: UniverRenderingContext, bounds?: IViewportInfo): void;
|
||||
|
||||
/**
|
||||
* TODO: DR-Univer, fix as unknown as
|
||||
@@ -94,7 +94,7 @@ export abstract class SheetComponent extends RenderComponent<SpreadsheetSkeleton
|
||||
}
|
||||
|
||||
export abstract class SpreadsheetHeader extends SheetComponent {
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportBound): void {
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportInfo): void {
|
||||
this.draw(ctx, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ import {
|
||||
isRectIntersect,
|
||||
mergeInfoOffset,
|
||||
} from '../../basics/tools';
|
||||
import type { IBoundRectNoAngle, IViewportBound } from '../../basics/vector2';
|
||||
import type { IBoundRectNoAngle, IViewportInfo } from '../../basics/vector2';
|
||||
import { columnIterator } from '../docs/layout/tools';
|
||||
import { DocumentSkeleton } from '../docs/layout/doc-skeleton';
|
||||
import { DocumentViewModel } from '../docs/view-model/document-view-model';
|
||||
@@ -219,6 +219,9 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
private _rowHeaderWidth = 0;
|
||||
private _columnHeaderHeight = 0;
|
||||
|
||||
/**
|
||||
* skeletonData(row col range) of visible area
|
||||
*/
|
||||
private _rowColumnSegment: IRowColumnSegment = {
|
||||
startRow: -1,
|
||||
endRow: -1,
|
||||
@@ -405,7 +408,7 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
this._marginTop = top;
|
||||
}
|
||||
|
||||
calculateSegment(bounds?: IViewportBound) {
|
||||
calculateSegment(bounds?: IViewportInfo) {
|
||||
if (!this._worksheetData) {
|
||||
return;
|
||||
}
|
||||
@@ -423,7 +426,7 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
return true;
|
||||
}
|
||||
|
||||
calculateWithoutClearingCache(bounds?: IViewportBound) {
|
||||
calculateWithoutClearingCache(bounds?: IViewportInfo) {
|
||||
if (!this.calculateSegment(bounds)) {
|
||||
return;
|
||||
}
|
||||
@@ -437,7 +440,7 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
return this;
|
||||
}
|
||||
|
||||
calculate(bounds?: IViewportBound) {
|
||||
calculate(bounds?: IViewportInfo) {
|
||||
this._resetCache();
|
||||
|
||||
this.calculateWithoutClearingCache(bounds);
|
||||
@@ -605,8 +608,9 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
return Math.max(rowHeader.width, widthByComputation);
|
||||
}
|
||||
|
||||
getRowColumnSegment(bounds?: IViewportBound) {
|
||||
return this._getBounding(this._rowHeightAccumulation, this._columnWidthAccumulation, bounds?.viewBound);
|
||||
getRowColumnSegment(bounds?: IViewportInfo) {
|
||||
return this._getBounding(this._rowHeightAccumulation, this._columnWidthAccumulation, bounds?.cacheBound);
|
||||
// return this._getBounding(this._rowHeightAccumulation, this._columnWidthAccumulation, bounds?.viewBound);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1378,8 +1382,8 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
const row_ed = searchArray(rowHeightAccumulation, Math.round(viewBound.bottom) - this.columnHeaderHeightAndMarginTop);
|
||||
|
||||
if (row_st === -1 && row_ed === 0) {
|
||||
dataset_row_st = -1;
|
||||
dataset_row_ed = -1;
|
||||
dataset_row_st = 0;
|
||||
dataset_row_ed = 0;
|
||||
} else {
|
||||
if (row_st === -1) {
|
||||
dataset_row_st = 0;
|
||||
@@ -1400,8 +1404,8 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
const col_ed = searchArray(columnWidthAccumulation, Math.round(viewBound.right) - this.rowHeaderWidthAndMarginLeft);
|
||||
|
||||
if (col_st === -1 && col_ed === 0) {
|
||||
dataset_col_st = -1;
|
||||
dataset_col_ed = -1;
|
||||
dataset_col_st = 0;
|
||||
dataset_col_ed = 0;
|
||||
} else {
|
||||
if (col_st === -1) {
|
||||
dataset_col_st = 0;
|
||||
@@ -1423,7 +1427,7 @@ export class SpreadsheetSkeleton extends Skeleton {
|
||||
endRow: dataset_row_ed,
|
||||
startColumn: dataset_col_st,
|
||||
endColumn: dataset_col_ed,
|
||||
};
|
||||
} as IRange;
|
||||
}
|
||||
|
||||
private _generateRowMatrixCache(
|
||||
|
||||
@@ -17,19 +17,16 @@
|
||||
import type { IRange, ISelectionCellWithCoord, Nullable, ObjectMatrix } from '@univerjs/core';
|
||||
import { BooleanNumber, sortRules } from '@univerjs/core';
|
||||
|
||||
import type { BaseObject } from '../../base-object';
|
||||
|
||||
import { FIX_ONE_PIXEL_BLUR_OFFSET, RENDER_CLASS_TYPE } from '../../basics/const';
|
||||
|
||||
// import { clearLineByBorderType } from '../../basics/draw';
|
||||
import { getCellPositionByIndex, getColor } from '../../basics/tools';
|
||||
import type { IViewportBound, Vector2 } from '../../basics/vector2';
|
||||
import { Canvas } from '../../canvas';
|
||||
import type { IBoundRectNoAngle, IViewportInfo, Vector2 } from '../../basics/vector2';
|
||||
import type { Canvas } from '../../canvas';
|
||||
import type { UniverRenderingContext } from '../../context';
|
||||
import type { Engine } from '../../engine';
|
||||
import type { Scene } from '../../scene';
|
||||
import type { SceneViewer } from '../../scene-viewer';
|
||||
import type { Viewport } from '../../viewport';
|
||||
import { Documents } from '../docs/document';
|
||||
import { SpreadsheetExtensionRegistry } from '../extension';
|
||||
import type { Background } from './extensions/background';
|
||||
@@ -39,6 +36,7 @@ import type { Font } from './extensions/font';
|
||||
// import type { BorderCacheItem } from './interfaces';
|
||||
import { SheetComponent } from './sheet-component';
|
||||
import type { SpreadsheetSkeleton } from './sheet-skeleton';
|
||||
import { type IPaintForRefresh, type IPaintForScrolling, SHEET_VIEWPORT_KEY } from './interfaces';
|
||||
|
||||
const OBJECT_KEY = '__SHEET_EXTENSION_FONT_DOCUMENT_INSTANCE__';
|
||||
|
||||
@@ -49,11 +47,9 @@ export class Spreadsheet extends SheetComponent {
|
||||
|
||||
private _fontExtension!: Font;
|
||||
|
||||
private _cacheCanvas!: Canvas;
|
||||
|
||||
private _refreshIncrementalState = false;
|
||||
|
||||
private _forceDirty = false;
|
||||
private _dirtyBounds: IBoundRectNoAngle[] = [];
|
||||
|
||||
private _forceDisableGridlines = false;
|
||||
|
||||
@@ -70,20 +66,8 @@ export class Spreadsheet extends SheetComponent {
|
||||
private _allowCache: boolean = true
|
||||
) {
|
||||
super(oKey, spreadsheetSkeleton);
|
||||
|
||||
if (this._allowCache) {
|
||||
this._cacheCanvas = new Canvas();
|
||||
|
||||
this.onIsAddedToParentObserver.add((parent) => {
|
||||
(parent as Scene)?.getEngine()?.onTransformChangeObservable.add(() => {
|
||||
this._resizeCacheCanvas();
|
||||
});
|
||||
this._resizeCacheCanvas();
|
||||
this._addMakeDirtyToScroll();
|
||||
});
|
||||
}
|
||||
|
||||
this._initialDefaultExtension();
|
||||
|
||||
this.makeDirty(true);
|
||||
}
|
||||
|
||||
@@ -118,14 +102,22 @@ export class Spreadsheet extends SheetComponent {
|
||||
super.dispose();
|
||||
this._documents?.dispose();
|
||||
this._documents = null as unknown as Documents;
|
||||
this._cacheCanvas?.dispose();
|
||||
this._cacheCanvas = null as unknown as Canvas;
|
||||
// cacheCanvas 已经移动到 viewport 中了, cacheCanvas 的 dispose 在 viewport@dispose 中处理
|
||||
// this._cacheCanvas?.dispose();
|
||||
// this._cacheCanvas = null as unknown as Canvas;
|
||||
this._backgroundExtension = null as unknown as Background;
|
||||
this._borderExtension = null as unknown as Border;
|
||||
this._fontExtension = null as unknown as Font;
|
||||
}
|
||||
|
||||
override draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
/**
|
||||
* draw by viewport
|
||||
* cacheBound ---> viewBound
|
||||
* diffCacheBounds ---> diffRange
|
||||
* @param ctx
|
||||
* @param viewportInfo
|
||||
*/
|
||||
override draw(ctx: UniverRenderingContext, viewportInfo: IViewportInfo) {
|
||||
// const { parent = { scaleX: 1, scaleY: 1 } } = this;
|
||||
// const mergeData = this.getMergeData();
|
||||
// const showGridlines = this.getShowGridlines() || 1;
|
||||
@@ -133,15 +125,23 @@ export class Spreadsheet extends SheetComponent {
|
||||
if (!spreadsheetSkeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._drawAuxiliary(ctx);
|
||||
const parentScale = this.getParentScale();
|
||||
|
||||
const diffRanges = this._refreshIncrementalState
|
||||
? bounds?.diffBounds.map((bound) => spreadsheetSkeleton.getRowColumnSegmentByViewBound(bound))
|
||||
const diffRanges = this._refreshIncrementalState && viewportInfo?.diffBounds
|
||||
? viewportInfo?.diffBounds?.map((bound) => spreadsheetSkeleton.getRowColumnSegmentByViewBound(bound))
|
||||
: undefined;
|
||||
const viewRanges = [spreadsheetSkeleton.getRowColumnSegmentByViewBound(viewportInfo?.cacheBound)];
|
||||
const extensions = this.getExtensionsByOrder();
|
||||
|
||||
// At this moment, ctx.transform is at topLeft of sheet content, cell(0, 0)
|
||||
for (const extension of extensions) {
|
||||
extension.draw(ctx, parentScale, spreadsheetSkeleton, diffRanges);
|
||||
// const timeKey = `extension ${viewportInfo.viewPortKey}:${extension.constructor.name}`;
|
||||
extension.draw(ctx, parentScale, spreadsheetSkeleton, diffRanges, {
|
||||
viewRanges,
|
||||
checkOutOfViewBound: true,
|
||||
viewportKey: viewportInfo.viewportKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,10 +163,6 @@ export class Spreadsheet extends SheetComponent {
|
||||
if (!spreadsheetSkeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this.onPointerDownObserver.add((evt) => {
|
||||
// evt.offsetX;
|
||||
// });
|
||||
const { rowHeightAccumulation, columnWidthAccumulation, rowHeaderWidth, columnHeaderHeight } =
|
||||
spreadsheetSkeleton;
|
||||
|
||||
@@ -196,8 +192,8 @@ export class Spreadsheet extends SheetComponent {
|
||||
let y = 0;
|
||||
const viewPort = scene.getActiveViewportByRelativeCoord(coord);
|
||||
if (viewPort) {
|
||||
const actualX = viewPort.actualScrollX || 0;
|
||||
const actualY = viewPort.actualScrollY || 0;
|
||||
const actualX = viewPort.viewportScrollX || 0;
|
||||
const actualY = viewPort.viewportScrollY || 0;
|
||||
x += actualX;
|
||||
y += actualY;
|
||||
}
|
||||
@@ -207,7 +203,15 @@ export class Spreadsheet extends SheetComponent {
|
||||
};
|
||||
}
|
||||
|
||||
makeForceDirty(state = true) {
|
||||
isForceDirty(): boolean {
|
||||
return this._forceDirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* canvas resize & zoom would call forceDirty
|
||||
* @param state
|
||||
*/
|
||||
override makeForceDirty(state = true) {
|
||||
this.makeDirty(state);
|
||||
this._forceDirty = state;
|
||||
}
|
||||
@@ -220,138 +224,201 @@ export class Spreadsheet extends SheetComponent {
|
||||
return this.getSkeleton()?.getMergeBounding(startRow, startColumn, endRow, endColumn);
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
/**
|
||||
* @param state
|
||||
*/
|
||||
override makeDirty(state: boolean = true) {
|
||||
(this.getParent() as Scene)?.getViewports().forEach((vp) => vp.markDirty(state));
|
||||
super.makeDirty(state);
|
||||
if (state === false) {
|
||||
this._dirtyBounds = [];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setDirtyArea(dirtyBounds: IBoundRectNoAngle[]) {
|
||||
this._dirtyBounds = dirtyBounds;
|
||||
}
|
||||
|
||||
renderByViewport(mainCtx: UniverRenderingContext, viewportInfo: IViewportInfo, spreadsheetSkeleton: SpreadsheetSkeleton) {
|
||||
const { diffBounds, diffX, diffY, viewPortPosition, cacheCanvas, leftOrigin, topOrigin, bufferEdgeX, bufferEdgeY, isDirty: isViewportDirty, isForceDirty: isViewportForceDirty } = viewportInfo as Required<IViewportInfo>;
|
||||
const { rowHeaderWidth, columnHeaderHeight } = spreadsheetSkeleton;
|
||||
const { a: scaleX = 1, d: scaleY = 1 } = mainCtx.getTransform();
|
||||
const bufferEdgeSizeX = bufferEdgeX * scaleX / window.devicePixelRatio;
|
||||
const bufferEdgeSizeY = bufferEdgeY * scaleY / window.devicePixelRatio;
|
||||
|
||||
const cacheCtx = cacheCanvas.getContext();
|
||||
cacheCtx.save();
|
||||
const { left, top, right, bottom } = viewPortPosition;
|
||||
const dw = right - left + rowHeaderWidth;
|
||||
const dh = bottom - top + columnHeaderHeight;
|
||||
const isForceDirty = isViewportForceDirty || this.isForceDirty();
|
||||
const isDirty = isViewportDirty || this.isDirty();
|
||||
if (diffBounds.length === 0 || (diffX === 0 && diffY === 0) || isForceDirty || isDirty) {
|
||||
if (isDirty || isForceDirty) {
|
||||
this.refreshCacheCanvas(viewportInfo, { cacheCanvas, cacheCtx, mainCtx, topOrigin, leftOrigin, bufferEdgeX, bufferEdgeY });
|
||||
}
|
||||
} else if (diffBounds.length !== 0 || diffX !== 0 || diffY !== 0) {
|
||||
// scrolling && no dirty
|
||||
this.paintNewAreaForScrolling(viewportInfo, {
|
||||
cacheCanvas, cacheCtx, mainCtx, topOrigin, leftOrigin, bufferEdgeX, bufferEdgeY, scaleX, scaleY, columnHeaderHeight, rowHeaderWidth,
|
||||
});
|
||||
}
|
||||
// support for browser native zoom (only windows has this problem)
|
||||
const sourceLeft = bufferEdgeSizeX * Math.min(1, window.devicePixelRatio);
|
||||
const sourceTop = bufferEdgeSizeY * Math.min(1, window.devicePixelRatio);
|
||||
this._applyCache(cacheCanvas, mainCtx, sourceLeft, sourceTop, dw, dh, left, top, dw, dh);
|
||||
cacheCtx.restore();
|
||||
}
|
||||
|
||||
paintNewAreaForScrolling(viewportInfo: IViewportInfo, param: IPaintForScrolling) {
|
||||
const { cacheCanvas, cacheCtx, mainCtx, topOrigin, leftOrigin, bufferEdgeX, bufferEdgeY, scaleX, scaleY, columnHeaderHeight, rowHeaderWidth } = param;
|
||||
const { shouldCacheUpdate, diffCacheBounds, diffX, diffY } = viewportInfo;
|
||||
cacheCtx.save();
|
||||
cacheCtx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
cacheCtx.globalCompositeOperation = 'copy';
|
||||
cacheCtx.drawImage(cacheCanvas.getCanvasEle(), diffX * scaleX, diffY * scaleY);
|
||||
cacheCtx.restore();
|
||||
|
||||
this._refreshIncrementalState = true;
|
||||
// Reset the ctx position to the spreadsheet content origin before drawing.
|
||||
// trasnlation should be (rowHeaderWidth, colHeaderHeight) at start.
|
||||
const m = mainCtx.getTransform();
|
||||
cacheCtx.setTransform(m.a, m.b, m.c, m.d, 0, 0);
|
||||
|
||||
// leftOrigin 是 viewport 相对 sheetcorner 的偏移(不考虑缩放)
|
||||
// - (leftOrigin - bufferEdgeX) ----> 简化 - leftOrigin + bufferEdgeX
|
||||
cacheCtx.translateWithPrecision(m.e / m.a - leftOrigin + bufferEdgeX, m.f / m.d - topOrigin + bufferEdgeY);
|
||||
|
||||
if (shouldCacheUpdate) {
|
||||
for (const diffBound of diffCacheBounds) {
|
||||
const { left: diffLeft, right: diffRight, bottom: diffBottom, top: diffTop } = diffBound;
|
||||
|
||||
// this.draw 的时候 ctx.translate 单元格偏移是相对 spreadsheet content
|
||||
// 但是 diffBounds 包括 rowHeader columnWidth, 因此绘制前需要减去行头列头的偏移
|
||||
const x = diffLeft - rowHeaderWidth;
|
||||
const y = diffTop - columnHeaderHeight;
|
||||
const w = diffRight - diffLeft;
|
||||
const h = diffBottom - diffTop; // w h 必须精确和 diffarea 大小匹配, 否则会造成往回滚时, clear 的区域过大, 导致上一帧有效内容被擦除
|
||||
|
||||
cacheCtx.clearRectByPrecision(x, y, w, h);
|
||||
// cacheCtx.fillStyle = this.testGetRandomLightColor();
|
||||
// cacheCtx.fillRectByPrecision(x, y, w, h); // x, y is diffBounds, means it's relative to scrolling distance.
|
||||
|
||||
cacheCtx.save();
|
||||
cacheCtx.beginPath();
|
||||
cacheCtx.rectByPrecision(x, y, w, h);
|
||||
cacheCtx.closePath();
|
||||
// The reason for clipping here is to avoid duplicate drawing (otherwise the text would be jagged, especially on Windows)
|
||||
cacheCtx.clip();
|
||||
this.draw(cacheCtx, {
|
||||
...viewportInfo,
|
||||
diffBounds: [diffBound],
|
||||
});
|
||||
cacheCtx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// this.testShowRuler(cacheCtx, viewportInfo);
|
||||
this._refreshIncrementalState = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw the entire viewport.
|
||||
*/
|
||||
refreshCacheCanvas(viewportInfo: IViewportInfo, param: IPaintForRefresh) {
|
||||
const { cacheCanvas, cacheCtx, mainCtx, topOrigin, leftOrigin, bufferEdgeX, bufferEdgeY } = param;
|
||||
cacheCtx.save();
|
||||
cacheCtx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
cacheCanvas.clear();
|
||||
cacheCtx.restore();
|
||||
|
||||
cacheCtx.save();
|
||||
// 所以 cacheCtx.setTransform 已经包含了 rowHeaderWidth + viewport + scroll 距离
|
||||
const m = mainCtx.getTransform();
|
||||
// cacheCtx.setTransform(m.a, m.b, m.c, m.d, m.e, m.f);
|
||||
cacheCtx.setTransform(m.a, m.b, m.c, m.d, 0, 0);
|
||||
|
||||
// The 'leftOrigin' is the offset of the viewport relative to the sheet corner, which is the position of cell(0, 0), and it does not consider scaling.
|
||||
// - (leftOrigin - bufferEdgeX) ----> - leftOrigin + bufferEdgeX
|
||||
cacheCtx.translateWithPrecision(m.e / m.a - leftOrigin + bufferEdgeX, m.f / m.d - topOrigin + bufferEdgeY);
|
||||
// extension 绘制时按照内容的左上角计算, 不考虑 rowHeaderWidth
|
||||
this.draw(cacheCtx, viewportInfo);
|
||||
// this.testShowRuler(cacheCtx, viewportInfo);
|
||||
cacheCtx.restore();
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, viewportInfo: IViewportInfo) {
|
||||
if (!this.visible) {
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
const spreadsheetSkeleton = this.getSkeleton();
|
||||
if (!spreadsheetSkeleton) return;
|
||||
|
||||
spreadsheetSkeleton.calculateWithoutClearingCache(bounds);
|
||||
if (!spreadsheetSkeleton) {
|
||||
return;
|
||||
}
|
||||
spreadsheetSkeleton.calculateWithoutClearingCache(viewportInfo);
|
||||
|
||||
const segment = spreadsheetSkeleton.rowColumnSegment;
|
||||
|
||||
if (
|
||||
(segment.startRow === -1 && segment.endRow === -1) ||
|
||||
(segment.startColumn === -1 && segment.endColumn === -1)
|
||||
(segment.startColumn === -1 && segment.endColumn === -1)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
mainCtx.save();
|
||||
|
||||
const { a: scaleX = 1, d: scaleY = 1 } = mainCtx.getTransform();
|
||||
|
||||
const { rowHeaderWidth, columnHeaderHeight } = spreadsheetSkeleton;
|
||||
|
||||
mainCtx.translateWithPrecision(rowHeaderWidth, columnHeaderHeight);
|
||||
|
||||
this._drawAuxiliary(mainCtx, bounds);
|
||||
const { viewportKey } = viewportInfo;
|
||||
// scene --> layer, getObjects --> viewport.render(object) --> spreadsheet
|
||||
// zIndex 0 spreadsheet this.getObjectsByOrder() ---> [spreadsheet]
|
||||
// zIndex 2 rowHeader & colHeader & freezeBorder this.getObjectsByOrder() ---> [SpreadsheetRowHeader, SpreadsheetColumnHeader, _Rect]
|
||||
// zIndex 3 selection this.getObjectsByOrder() ---> [group]
|
||||
|
||||
if (bounds && this._allowCache === true) {
|
||||
const { viewBound, diffBounds, diffX, diffY, viewPortPosition, viewPortKey } = bounds;
|
||||
|
||||
// if (viewPortKey === 'viewColumnRight' || viewPortKey === 'viewRowBottom' || viewPortKey === 'viewLeftTop') {
|
||||
// // console.warn('ignore object', this);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (
|
||||
// viewPortKey === 'viewRowTop' ||
|
||||
// viewPortKey === 'viewRowBottom' ||
|
||||
// viewPortKey === 'viewMainLeft' ||
|
||||
// viewPortKey === 'viewMainTop'
|
||||
// ) {
|
||||
// // console.warn('ignore object', this);
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (viewPortKey === 'viewMain') {
|
||||
const ctx = this._cacheCanvas.getContext();
|
||||
ctx.save();
|
||||
|
||||
const { left, top, right, bottom } = viewPortPosition;
|
||||
|
||||
const dw = right - left + rowHeaderWidth;
|
||||
|
||||
const dh = bottom - top + columnHeaderHeight;
|
||||
|
||||
if (diffBounds.length === 0 || (diffX === 0 && diffY === 0) || this._forceDirty) {
|
||||
if (this.isDirty() || this._forceDirty) {
|
||||
this._cacheCanvas.clear();
|
||||
ctx.setTransform(mainCtx.getTransform());
|
||||
this._draw(ctx, bounds);
|
||||
this._forceDirty = false;
|
||||
}
|
||||
this._applyCache(mainCtx, left, top, dw, dh, left, top, dw, dh);
|
||||
} else {
|
||||
if (this.isDirty()) {
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'copy';
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.drawImage(this._cacheCanvas.getCanvasEle(), diffX * scaleX, diffY * scaleY);
|
||||
ctx.restore();
|
||||
|
||||
this._refreshIncrementalState = true;
|
||||
ctx.setTransform(mainCtx.getTransform());
|
||||
|
||||
for (const diffBound of diffBounds) {
|
||||
const { left: diffLeft, right: diffRight, bottom: diffBottom, top: diffTop } = diffBound;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rectByPrecision(
|
||||
diffLeft - rowHeaderWidth - FIX_ONE_PIXEL_BLUR_OFFSET,
|
||||
diffTop - columnHeaderHeight - FIX_ONE_PIXEL_BLUR_OFFSET,
|
||||
diffRight - diffLeft + rowHeaderWidth + FIX_ONE_PIXEL_BLUR_OFFSET * 2,
|
||||
diffBottom - diffTop + columnHeaderHeight + FIX_ONE_PIXEL_BLUR_OFFSET * 2
|
||||
);
|
||||
// ctx.fillStyle = 'rgb(0,0,0)';
|
||||
|
||||
ctx.clip();
|
||||
this._draw(ctx, {
|
||||
viewBound: bounds.viewBound,
|
||||
diffBounds: [diffBound],
|
||||
diffX: bounds.diffX,
|
||||
diffY: bounds.diffY,
|
||||
viewPortPosition: bounds.viewPortPosition,
|
||||
viewPortKey: bounds.viewPortKey,
|
||||
});
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
this._refreshIncrementalState = false;
|
||||
}
|
||||
this._applyCache(mainCtx, left, top, dw, dh, left, top, dw, dh);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
// SpreadsheetRowHeader SpreadsheetColumnHeader is not render by spreadsheet
|
||||
if (this.sheetContentViewport().includes(viewportKey as SHEET_VIEWPORT_KEY)) {
|
||||
if (viewportInfo && viewportInfo.cacheCanvas) {
|
||||
this.renderByViewport(mainCtx, viewportInfo, spreadsheetSkeleton);
|
||||
} else {
|
||||
this._draw(mainCtx, bounds);
|
||||
this._draw(mainCtx, viewportInfo);
|
||||
}
|
||||
} else if (this.sheetHeaderViewport().includes(viewportKey as SHEET_VIEWPORT_KEY)) {
|
||||
// doing nothing, other components(SpreadsheetRowHeader...) will render
|
||||
} else {
|
||||
this._draw(mainCtx, bounds);
|
||||
// embed in doc & slide
|
||||
// 现阶段 embed 开启贴图有问题, 3f12ad80188a83283bcd95c65e6c5dcc2d23ad72
|
||||
if (viewportInfo && viewportInfo.cacheCanvas) {
|
||||
this.renderByViewport(mainCtx, viewportInfo, spreadsheetSkeleton);
|
||||
} else {
|
||||
this._draw(mainCtx, viewportInfo);
|
||||
}
|
||||
}
|
||||
|
||||
mainCtx.restore();
|
||||
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private _resizeCacheCanvas() {
|
||||
const parentSize = this._getAncestorSize();
|
||||
if (!parentSize || this._cacheCanvas == null) {
|
||||
return;
|
||||
}
|
||||
const { width, height } = parentSize;
|
||||
this._cacheCanvas.setSize(width, height);
|
||||
this.makeForceDirty(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* applyCache from cache canvas
|
||||
* @param cacheCanvas Source Image
|
||||
* @param ctx MainCtx
|
||||
* @param sx
|
||||
* @param sy
|
||||
* @param sw
|
||||
* @param sh
|
||||
* @param dx
|
||||
* @param dy
|
||||
* @param dw
|
||||
* @param dh
|
||||
*/
|
||||
protected _applyCache(
|
||||
ctx?: UniverRenderingContext,
|
||||
cacheCanvas: Canvas,
|
||||
ctx: UniverRenderingContext,
|
||||
sx: number = 0,
|
||||
sy: number = 0,
|
||||
sw: number = 0,
|
||||
@@ -365,21 +432,26 @@ export class Spreadsheet extends SheetComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const pixelRatio = this._cacheCanvas.getPixelRatio();
|
||||
|
||||
const cacheCtx = this._cacheCanvas.getContext();
|
||||
const pixelRatio = cacheCanvas.getPixelRatio();
|
||||
const cacheCtx = cacheCanvas.getContext();
|
||||
cacheCtx.save();
|
||||
ctx.save();
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
cacheCtx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
const fn = (num: number, scale: number) => {
|
||||
return Math.round(num * scale);
|
||||
};
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
// ctx.imageSmoothingEnabled = true;
|
||||
// ctx.imageSmoothingQuality = 'high';
|
||||
ctx.drawImage(
|
||||
this._cacheCanvas.getCanvasEle(),
|
||||
sx * pixelRatio,
|
||||
sy * pixelRatio,
|
||||
cacheCanvas.getCanvasEle(),
|
||||
fn(sx, pixelRatio),
|
||||
fn(sy, pixelRatio),
|
||||
sw * pixelRatio,
|
||||
sh * pixelRatio,
|
||||
dx * pixelRatio,
|
||||
dy * pixelRatio,
|
||||
fn(dx, pixelRatio),
|
||||
fn(dy, pixelRatio),
|
||||
dw * pixelRatio,
|
||||
dh * pixelRatio
|
||||
);
|
||||
@@ -387,8 +459,8 @@ export class Spreadsheet extends SheetComponent {
|
||||
cacheCtx.restore();
|
||||
}
|
||||
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
this.draw(ctx, bounds);
|
||||
protected override _draw(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
this.draw(ctx, bounds!);
|
||||
}
|
||||
|
||||
private _getAncestorSize() {
|
||||
@@ -434,49 +506,18 @@ export class Spreadsheet extends SheetComponent {
|
||||
this._fontExtension = this.getExtensionByKey('DefaultFontExtension') as Font;
|
||||
}
|
||||
|
||||
private _addMakeDirtyToScroll() {
|
||||
this._hasScrollViewportOperator(this, (viewport: Viewport) => {
|
||||
viewport.onScrollBeforeObserver.add(() => {
|
||||
this.makeDirty(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _hasScrollViewportOperator(object: BaseObject, fn: (viewPort: Viewport) => void) {
|
||||
let parent: any = object.getParent();
|
||||
while (parent) {
|
||||
if (parent.classType === RENDER_CLASS_TYPE.SCENE) {
|
||||
const viewports = parent.getViewports();
|
||||
const viewPorts = this._getHasScrollViewports(viewports);
|
||||
for (const viewport of viewPorts) {
|
||||
if (viewport) {
|
||||
fn(viewport);
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = parent?.getParent && parent?.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
private _getHasScrollViewports(viewports: Viewport[]) {
|
||||
const newViewports: Viewport[] = [];
|
||||
for (const viewport of viewports) {
|
||||
const scrollBar = viewport.getScrollBar();
|
||||
if (scrollBar) {
|
||||
newViewports.push(viewport);
|
||||
}
|
||||
}
|
||||
return newViewports;
|
||||
}
|
||||
|
||||
private _drawAuxiliary(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
/**
|
||||
* draw gridlines
|
||||
* @param ctx
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _drawAuxiliary(ctx: UniverRenderingContext) {
|
||||
const spreadsheetSkeleton = this.getSkeleton();
|
||||
if (spreadsheetSkeleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { rowColumnSegment, dataMergeCache, overflowCache, stylesCache, showGridlines } = spreadsheetSkeleton;
|
||||
const { border, backgroundPositions } = stylesCache;
|
||||
const { rowColumnSegment, dataMergeCache, overflowCache, showGridlines } = spreadsheetSkeleton;
|
||||
const { startRow, endRow, startColumn, endColumn } = rowColumnSegment;
|
||||
if (!spreadsheetSkeleton || showGridlines === BooleanNumber.FALSE || this._forceDisableGridlines) {
|
||||
return;
|
||||
@@ -592,7 +633,7 @@ export class Spreadsheet extends SheetComponent {
|
||||
// overflow cell
|
||||
this._clearRectangle(ctx, rowHeightAccumulation, columnWidthAccumulation, overflowCache.toNativeArray());
|
||||
|
||||
this._clearBackground(ctx, backgroundPositions);
|
||||
// this._clearBackground(ctx, backgroundPositions);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -651,4 +692,88 @@ export class Spreadsheet extends SheetComponent {
|
||||
ctx.clearRectForTexture(startX, startY, endX - startX + 0.5, endY - startY + 0.5);
|
||||
});
|
||||
}
|
||||
|
||||
sheetContentViewport() {
|
||||
return [SHEET_VIEWPORT_KEY.VIEW_MAIN, SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT_TOP, SHEET_VIEWPORT_KEY.VIEW_MAIN_TOP, SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT];
|
||||
}
|
||||
|
||||
sheetHeaderViewport() {
|
||||
return [SHEET_VIEWPORT_KEY.VIEW_ROW_TOP, SHEET_VIEWPORT_KEY.VIEW_ROW_BOTTOM, SHEET_VIEWPORT_KEY.VIEW_COLUMN_LEFT, SHEET_VIEWPORT_KEY.VIEW_COLUMN_RIGHT, SHEET_VIEWPORT_KEY.VIEW_LEFT_TOP];
|
||||
}
|
||||
|
||||
testShowRuler(cacheCtx: UniverRenderingContext, viewportInfo: IViewportInfo): void {
|
||||
const { cacheBound } = viewportInfo;
|
||||
const spreadsheetSkeleton = this.getSkeleton()!;
|
||||
const { rowHeaderWidth, columnHeaderHeight } = spreadsheetSkeleton;
|
||||
const { left, top, right, bottom } = cacheBound;
|
||||
// left -= rowHeaderWidth;
|
||||
// top -= columnHeaderHeight;
|
||||
// right -= rowHeaderWidth;
|
||||
// bottom -= columnHeaderHeight;
|
||||
const findClosestHundred = (number: number) => {
|
||||
const remainder = number % 100;
|
||||
return number + (100 - remainder);
|
||||
};
|
||||
const startX = findClosestHundred(left);
|
||||
const endX = findClosestHundred(right);
|
||||
const startY = findClosestHundred(top);
|
||||
const endY = findClosestHundred(bottom);
|
||||
cacheCtx.save();
|
||||
cacheCtx.beginPath();
|
||||
cacheCtx.strokeStyle = '#000000';
|
||||
cacheCtx.fillStyle = '#000000';
|
||||
cacheCtx.font = '16px Arial';
|
||||
cacheCtx.lineWidth = 1;
|
||||
cacheCtx.textAlign = 'center';
|
||||
cacheCtx.textBaseline = 'middle';
|
||||
|
||||
for (let i = startX; i <= endX; i += 50) {
|
||||
cacheCtx.beginPath();
|
||||
cacheCtx.strokeStyle = (i % 100 === 0) ? 'red' : '#aaa';
|
||||
cacheCtx.moveTo(i - rowHeaderWidth, top - columnHeaderHeight);
|
||||
cacheCtx.lineTo(i - rowHeaderWidth, bottom - columnHeaderHeight);
|
||||
cacheCtx.stroke();
|
||||
cacheCtx.closePath();
|
||||
}
|
||||
for (let j = startY; j <= endY; j += 50) {
|
||||
cacheCtx.beginPath();
|
||||
cacheCtx.strokeStyle = (j % 100 === 0) ? 'red' : '#aaa';
|
||||
cacheCtx.moveTo(left - rowHeaderWidth, j - columnHeaderHeight);
|
||||
cacheCtx.lineTo(right - rowHeaderWidth, j - columnHeaderHeight);
|
||||
cacheCtx.stroke();
|
||||
cacheCtx.closePath();
|
||||
}
|
||||
cacheCtx.fillStyle = '#666';
|
||||
for (let i = startX; i <= endX; i += 100) {
|
||||
for (let j = startY; j <= endY; j += 100) {
|
||||
cacheCtx.fillText(`${i},${j}`, i - rowHeaderWidth, j - columnHeaderHeight);
|
||||
}
|
||||
}
|
||||
// start
|
||||
// cacheCtx.textAlign = 'left';
|
||||
// for (let j = startY; j <= endY; j += 100) {
|
||||
// cacheCtx.clearRect(left - rowHeaderWidth, j - columnHeaderHeight - 15, 30, 30);
|
||||
// cacheCtx.fillText(`${left}`, left - rowHeaderWidth, j - columnHeaderHeight);
|
||||
// }
|
||||
cacheCtx.closePath();
|
||||
cacheCtx.restore();
|
||||
}
|
||||
|
||||
testGetRandomLightColor(): string {
|
||||
const letters = 'ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 6)];
|
||||
}
|
||||
|
||||
// 确保生成的颜色足够亮
|
||||
const r = Number.parseInt(color.substring(1, 3), 16);
|
||||
const g = Number.parseInt(color.substring(3, 5), 16);
|
||||
const b = Number.parseInt(color.substring(5, 7), 16);
|
||||
if (r + g + b < 610) {
|
||||
return this.testGetRandomLightColor(); // 递归调用直到生成足够亮的颜色
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ export class UniverRenderingContext2D implements CanvasRenderingContext2D {
|
||||
* @method
|
||||
*/
|
||||
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterClockwise?: boolean) {
|
||||
this._context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
|
||||
this._context.arc(x, y, Math.max(0, radius), startAngle, endAngle, counterClockwise);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { BaseObject } from '../base-object';
|
||||
import type { IViewportBound, Vector2 } from '../basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from '../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../context';
|
||||
|
||||
export class CustomObject extends BaseObject {
|
||||
@@ -33,7 +33,7 @@ export class CustomObject extends BaseObject {
|
||||
};
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
if (!this.visible) {
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
|
||||
@@ -21,7 +21,7 @@ import { BaseObject } from './base-object';
|
||||
import type { CURSOR_TYPE } from './basics/const';
|
||||
import { RENDER_CLASS_TYPE } from './basics/const';
|
||||
import { isString } from './basics/tools';
|
||||
import type { IViewportBound, Vector2 } from './basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from './basics/vector2';
|
||||
import type { UniverRenderingContext } from './context';
|
||||
import type { ThinScene } from './thin-scene';
|
||||
|
||||
@@ -115,7 +115,7 @@ export class Group extends BaseObject {
|
||||
return this._objects;
|
||||
}
|
||||
|
||||
override render(ctx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override render(ctx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
ctx.save();
|
||||
const m = this.transform.getMatrix();
|
||||
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||
@@ -160,7 +160,7 @@ export class Group extends BaseObject {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _transformBounds(bounds?: IViewportBound) {
|
||||
private _transformBounds(bounds?: IViewportInfo) {
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import { BaseObject } from './base-object';
|
||||
import { RENDER_CLASS_TYPE } from './basics/const';
|
||||
import { Canvas } from './canvas';
|
||||
import type { UniverRenderingContext } from './context';
|
||||
import type { ThinScene } from './thin-scene';
|
||||
import type { Scene } from './scene';
|
||||
import type { SceneViewer } from './scene-viewer';
|
||||
|
||||
export class Layer extends Disposable {
|
||||
private _objects: BaseObject[] = [];
|
||||
@@ -33,7 +34,7 @@ export class Layer extends Disposable {
|
||||
private _debounceDirtyFunc: Nullable<() => void>;
|
||||
|
||||
constructor(
|
||||
private _scene: ThinScene,
|
||||
private _scene: Scene,
|
||||
objects: BaseObject[] = [],
|
||||
private _zIndex: number = 1,
|
||||
private _allowCache: boolean = false
|
||||
@@ -176,7 +177,7 @@ export class Layer extends Disposable {
|
||||
*/
|
||||
const parent = this.scene.getParent();
|
||||
if (parent.classType === RENDER_CLASS_TYPE.SCENE_VIEWER) {
|
||||
parent.makeDirty(true);
|
||||
(parent as SceneViewer).makeDirty(true);
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -200,25 +201,26 @@ export class Layer extends Disposable {
|
||||
|
||||
render(parentCtx?: UniverRenderingContext, isMaxLayer = false) {
|
||||
const mainCtx = parentCtx || this._scene.getEngine()?.getCanvas().getContext();
|
||||
if (mainCtx) {
|
||||
if (this._allowCache && this._cacheCanvas) {
|
||||
if (this.isDirty()) {
|
||||
const ctx = this._cacheCanvas.getContext();
|
||||
|
||||
if (this._allowCache && this._cacheCanvas) {
|
||||
if (this.isDirty()) {
|
||||
const ctx = this._cacheCanvas.getContext();
|
||||
this._cacheCanvas.clear();
|
||||
|
||||
this._cacheCanvas.clear();
|
||||
ctx.save();
|
||||
|
||||
ctx.save();
|
||||
ctx.setTransform(mainCtx.getTransform());
|
||||
this._draw(ctx, isMaxLayer);
|
||||
|
||||
ctx.setTransform(mainCtx.getTransform());
|
||||
this._draw(ctx, isMaxLayer);
|
||||
|
||||
ctx.restore();
|
||||
ctx.restore();
|
||||
}
|
||||
this._applyCache(mainCtx);
|
||||
} else {
|
||||
mainCtx.save();
|
||||
this._draw(mainCtx, isMaxLayer);
|
||||
mainCtx.restore();
|
||||
}
|
||||
this._applyCache(mainCtx);
|
||||
} else {
|
||||
mainCtx.save();
|
||||
this._draw(mainCtx, isMaxLayer);
|
||||
mainCtx.restore();
|
||||
}
|
||||
|
||||
this.makeDirty(false);
|
||||
@@ -241,7 +243,7 @@ export class Layer extends Disposable {
|
||||
this._cacheCanvas = new Canvas();
|
||||
this.disposeWithMe(
|
||||
toDisposable(
|
||||
this._scene.getEngine().onTransformChangeObservable.add(() => {
|
||||
this._scene.getEngine()?.onTransformChangeObservable.add(() => {
|
||||
this._resizeCacheCanvas();
|
||||
})
|
||||
)
|
||||
@@ -249,7 +251,15 @@ export class Layer extends Disposable {
|
||||
}
|
||||
|
||||
private _draw(mainCtx: UniverRenderingContext, isMaxLayer: boolean) {
|
||||
this._scene.getViewports()?.forEach((vp) => vp.render(mainCtx, this.getObjectsByOrder(), isMaxLayer));
|
||||
const viewports = this._scene.getViewports().filter((vp) => vp.shouldIntoRender());
|
||||
const objects = this.getObjectsByOrder();
|
||||
for (const [_index, vp] of viewports.entries()) {
|
||||
vp.render(mainCtx, objects, isMaxLayer);
|
||||
}
|
||||
objects.forEach((o) => {
|
||||
o.makeDirty(false);
|
||||
o.makeForceDirty?.(false);
|
||||
});
|
||||
}
|
||||
|
||||
private _applyCache(ctx?: UniverRenderingContext) {
|
||||
@@ -258,12 +268,17 @@ export class Layer extends Disposable {
|
||||
}
|
||||
const width = this._cacheCanvas.getWidth();
|
||||
const height = this._cacheCanvas.getHeight();
|
||||
ctx.drawImage(this._cacheCanvas.getCanvasEle(), 0, 0, width, height);
|
||||
// it throw an error if canvas size is zero, and canvas size is zero when viewport isActive is false.
|
||||
if (width !== 0 && height !== 0) {
|
||||
ctx.drawImage(this._cacheCanvas.getCanvasEle(), 0, 0, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private _resizeCacheCanvas() {
|
||||
const engine = this._scene.getEngine();
|
||||
this._cacheCanvas?.setSize(engine.width, engine.height);
|
||||
if (engine) {
|
||||
this._cacheCanvas?.setSize(engine.width, engine.height);
|
||||
}
|
||||
this.makeDirty(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { IKeyValue, Nullable } from '@univerjs/core';
|
||||
import { BaseObject } from './base-object';
|
||||
import { RENDER_CLASS_TYPE } from './basics/const';
|
||||
import type { IObjectFullState } from './basics/interfaces';
|
||||
import type { IViewportBound, Vector2 } from './basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from './basics/vector2';
|
||||
import type { UniverRenderingContext } from './context';
|
||||
import type { ThinScene } from './thin-scene';
|
||||
|
||||
@@ -40,14 +40,14 @@ export class SceneViewer extends BaseObject {
|
||||
return RENDER_CLASS_TYPE.SCENE_VIEWER;
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
if (!this.visible) {
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (bounds) {
|
||||
const { left, top, right, bottom } = bounds.viewBound;
|
||||
const { left, top, right, bottom } = bounds.cacheBound || bounds.viewBound;
|
||||
|
||||
if (
|
||||
this.width + this.strokeWidth + this.left < left ||
|
||||
|
||||
@@ -460,7 +460,7 @@ export class Scene extends ThinScene {
|
||||
override removeViewport(key: string) {
|
||||
for (let i = 0, len = this._viewports.length; i < len; i++) {
|
||||
const viewport = this._viewports[i];
|
||||
if (viewport.viewPortKey === key) {
|
||||
if (viewport.viewportKey === key) {
|
||||
this._viewports.splice(i, 1);
|
||||
return viewport;
|
||||
}
|
||||
@@ -473,7 +473,7 @@ export class Scene extends ThinScene {
|
||||
|
||||
getViewport(key: string) {
|
||||
for (const viewport of this._viewports) {
|
||||
if (viewport.viewPortKey === key) {
|
||||
if (viewport.viewportKey === key) {
|
||||
return viewport;
|
||||
}
|
||||
}
|
||||
@@ -491,8 +491,6 @@ export class Scene extends ThinScene {
|
||||
for (let i = 0, len = layers.length; i < len; i++) {
|
||||
layers[i].render(parentCtx, i === len - 1);
|
||||
}
|
||||
|
||||
// this.getViewports()?.forEach((vp: Viewport) => vp.render(parentCtx));
|
||||
}
|
||||
|
||||
async requestRender(parentCtx?: UniverRenderingContext) {
|
||||
@@ -565,8 +563,8 @@ export class Scene extends ThinScene {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
if (viewPort) {
|
||||
const actualX = viewPort.actualScrollX || 0;
|
||||
const actualY = viewPort.actualScrollY || 0;
|
||||
const actualX = viewPort.viewportScrollX || 0;
|
||||
const actualY = viewPort.viewportScrollY || 0;
|
||||
x += actualX;
|
||||
y += actualY;
|
||||
}
|
||||
@@ -600,8 +598,8 @@ export class Scene extends ThinScene {
|
||||
const scene = parent as Scene;
|
||||
const viewPort = scene.getActiveViewportByCoord(coord);
|
||||
if (viewPort) {
|
||||
const actualX = viewPort.actualScrollX || 0;
|
||||
const actualY = viewPort.actualScrollY || 0;
|
||||
const actualX = viewPort.viewportScrollX || 0;
|
||||
const actualY = viewPort.viewportScrollY || 0;
|
||||
coord = coord.addByPoint(actualX, actualY);
|
||||
}
|
||||
} else if (parent.classType === RENDER_CLASS_TYPE.SCENE_VIEWER) {
|
||||
|
||||
@@ -45,10 +45,12 @@ export class Picture extends Shape<IPictureProps> {
|
||||
};
|
||||
if (config.image) {
|
||||
this._native = config.image;
|
||||
this._native.crossOrigin = 'anonymous';
|
||||
this._init();
|
||||
} else if (config.url) {
|
||||
this._native = document.createElement('img');
|
||||
this._native.src = config.url;
|
||||
this._native.crossOrigin = 'anonymous';
|
||||
this._native.onload = () => {
|
||||
config.success?.();
|
||||
this._init();
|
||||
|
||||
@@ -29,7 +29,7 @@ import { BooleanNumber, DEFAULT_EMPTY_DOCUMENT_VALUE, DocumentDataModel } from '
|
||||
|
||||
import { BaseObject } from '../base-object';
|
||||
import { TRANSFORM_CHANGE_OBSERVABLE_TYPE } from '../basics/interfaces';
|
||||
import type { IViewportBound } from '../basics/vector2';
|
||||
import type { IViewportInfo } from '../basics/vector2';
|
||||
import { DocumentSkeleton } from '../components/docs/layout/doc-skeleton';
|
||||
import { Documents } from '../components/docs/document';
|
||||
import { DocumentViewModel } from '../components/docs/view-model/document-view-model';
|
||||
@@ -167,7 +167,7 @@ export class RichText extends BaseObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
if (!this.visible) {
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { IKeyValue, IScale, Nullable } from '@univerjs/core';
|
||||
import { BASE_OBJECT_ARRAY, BaseObject } from '../base-object';
|
||||
import { SHAPE_TYPE } from '../basics/const';
|
||||
import type { IObjectFullState } from '../basics/interfaces';
|
||||
import type { IViewportBound, Vector2 } from '../basics/vector2';
|
||||
import type { IViewportInfo, Vector2 } from '../basics/vector2';
|
||||
import type { UniverRenderingContext } from '../context';
|
||||
|
||||
export type LineJoin = 'round' | 'bevel' | 'miter';
|
||||
@@ -304,7 +304,7 @@ export abstract class Shape<T> extends BaseObject {
|
||||
ctx.strokeStyle = stroke!;
|
||||
}
|
||||
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportBound) {
|
||||
override render(mainCtx: UniverRenderingContext, bounds?: IViewportInfo) {
|
||||
if (!this.visible) {
|
||||
this.makeDirty(false);
|
||||
return this;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
vi.stubGlobal('jest', vi);
|
||||
@@ -88,11 +88,11 @@
|
||||
"@univerjs/sheets-ui": "workspace:*",
|
||||
"@univerjs/ui": "workspace:*",
|
||||
"@wendellhu/redi": "0.15.2",
|
||||
"jest-canvas-mock": "^2.5.2",
|
||||
"jsdom": "^24.0.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
"vitest": "^1.6.0",
|
||||
"vitest-canvas-mock": "^0.3.3"
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { ICellData, IStyleData, Nullable } from '@univerjs/core';
|
||||
import { ICommandService, IUniverInstanceService } from '@univerjs/core';
|
||||
import { SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand } from '@univerjs/sheets';
|
||||
import type { Injector } from '@wendellhu/redi';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { RenderComponentType, SheetComponent } from '@univerjs/engine-render';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
@@ -48,10 +48,6 @@ describe('Test FUniver', () => {
|
||||
) => Nullable<IStyleData>;
|
||||
let getSheetRenderComponent: (unitId: string, viewKey: SHEET_VIEW_KEY) => Nullable<RenderComponentType>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('jest', vi);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createFacadeTestBed();
|
||||
get = testBed.get;
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('Test FRange', () => {
|
||||
it('Range setValue', () => {
|
||||
const activeSheet = univerAPI.getActiveWorkbook()?.getActiveSheet();
|
||||
|
||||
// A1 sets the number
|
||||
// // A1 sets the number
|
||||
const range1 = activeSheet?.getRange(0, 0, 1, 1);
|
||||
range1?.setValue(1);
|
||||
|
||||
@@ -190,7 +190,6 @@ describe('Test FRange', () => {
|
||||
activeSheet?.getRange(0, 0)?.setBackgroundColor('red');
|
||||
const range = activeSheet?.getRange(0, 0);
|
||||
expect(range?.getCellStyleData()?.bg?.rgb).toBe('red');
|
||||
|
||||
activeSheet?.getRange(0, 0, 2, 2)?.setFontWeight('bold');
|
||||
expect(range?.getCellStyleData()?.bl).toBe(1);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { ICellData, IStyleData, Nullable, UnitModel, Workbook } from '@univ
|
||||
import { ICommandService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { SetHorizontalTextAlignCommand, SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand, SetTextWrapCommand, SetVerticalTextAlignCommand } from '@univerjs/sheets';
|
||||
import type { Injector } from '@wendellhu/redi';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import type { IDragCellPosition, IHoverCellPosition } from '@univerjs/sheets-ui';
|
||||
@@ -77,10 +77,6 @@ describe('Test FSheetHooks', () => {
|
||||
let sheetHooks: FSheetHooks;
|
||||
let workbook: Workbook;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('jest', vi);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Initialize the subject
|
||||
hoverCurrentCell$ = new Subject<Nullable<IHoverCellPosition>>();
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { ICellData, IStyleData, Nullable } from '@univerjs/core';
|
||||
import { ICommandService, IUniverInstanceService } from '@univerjs/core';
|
||||
import { InsertSheetCommand, InsertSheetMutation, RemoveSheetCommand, RemoveSheetMutation, SetHorizontalTextAlignCommand, SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand, SetTextWrapCommand, SetVerticalTextAlignCommand, SetWorksheetActiveOperation } from '@univerjs/sheets';
|
||||
import type { Injector } from '@wendellhu/redi';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import type { FUniver } from '../../facade';
|
||||
import { createFacadeTestBed } from '../../__tests__/create-test-bed';
|
||||
@@ -40,10 +40,6 @@ describe('Test FWorkbook', () => {
|
||||
endColumn: number
|
||||
) => Nullable<IStyleData>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('jest', vi);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createFacadeTestBed();
|
||||
get = testBed.get;
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { ICellData, IStyleData, Nullable } from '@univerjs/core';
|
||||
import { ICommandService, IUniverInstanceService } from '@univerjs/core';
|
||||
import { SetHorizontalTextAlignCommand, SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand, SetTextWrapCommand, SetVerticalTextAlignCommand } from '@univerjs/sheets';
|
||||
import type { Injector } from '@wendellhu/redi';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import type { FUniver } from '../../facade';
|
||||
import { createFacadeTestBed } from '../../__tests__/create-test-bed';
|
||||
@@ -40,10 +40,6 @@ describe('Test FWorksheet', () => {
|
||||
endColumn: number
|
||||
) => Nullable<IStyleData>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('jest', vi);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createFacadeTestBed();
|
||||
get = testBed.get;
|
||||
|
||||
@@ -3,25 +3,11 @@ import pkg from './package.json';
|
||||
|
||||
export default ({ mode }) => createViteConfig({
|
||||
test: {
|
||||
setupFiles: ['./vitest.setup.ts'],
|
||||
setupFiles: [
|
||||
'./jest.setup.ts',
|
||||
'./vitest.setup.ts',
|
||||
],
|
||||
environment: 'jsdom',
|
||||
deps: {
|
||||
optimizer: {
|
||||
web: {
|
||||
include: ['vitest-canvas-mock'],
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOptions: {
|
||||
threads: {
|
||||
singleThread: true,
|
||||
},
|
||||
},
|
||||
environmentOptions: {
|
||||
jsdom: {
|
||||
resources: 'usable',
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
mode,
|
||||
|
||||
@@ -14,5 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'vitest-canvas-mock';
|
||||
import getCanvasWindow from 'jest-canvas-mock/lib/window';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
const apis = [
|
||||
'Path2D',
|
||||
'CanvasGradient',
|
||||
'CanvasPattern',
|
||||
'CanvasRenderingContext2D',
|
||||
'DOMMatrix',
|
||||
'ImageData',
|
||||
'TextMetrics',
|
||||
'ImageBitmap',
|
||||
'createImageBitmap',
|
||||
] as const;
|
||||
|
||||
const canvasWindow = getCanvasWindow({ document: window.document });
|
||||
|
||||
apis.forEach((api) => {
|
||||
vi.stubGlobal(api, canvasWindow[api]);
|
||||
});
|
||||
|
||||
@@ -20,10 +20,10 @@ import { INTERCEPTOR_POINT, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { Inject } from '@wendellhu/redi';
|
||||
import { bufferTime, filter } from 'rxjs/operators';
|
||||
import type { ISheetFontRenderExtension, Spreadsheet } from '@univerjs/engine-render';
|
||||
import type { ISheetFontRenderExtension } from '@univerjs/engine-render';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
|
||||
import { ConditionalFormattingIcon, ConditionalFormattingRuleModel, ConditionalFormattingService, ConditionalFormattingViewModel, DataBar, dataBarUKey, DEFAULT_PADDING, DEFAULT_WIDTH, IconUKey } from '@univerjs/sheets-conditional-formatting';
|
||||
import { ConditionalFormattingRuleModel, ConditionalFormattingService, ConditionalFormattingViewModel, DEFAULT_PADDING, DEFAULT_WIDTH } from '@univerjs/sheets-conditional-formatting';
|
||||
|
||||
import type { IConditionalFormattingCellData } from '@univerjs/sheets-conditional-formatting';
|
||||
|
||||
@@ -42,29 +42,6 @@ export class RenderController extends Disposable {
|
||||
super();
|
||||
this._initViewModelInterceptor();
|
||||
this._initSkeleton();
|
||||
this._initRender();
|
||||
}
|
||||
|
||||
_initRender() {
|
||||
const list: [string, new () => any][] = [[dataBarUKey, DataBar], [IconUKey, ConditionalFormattingIcon]];
|
||||
const register = (renderId: string) => {
|
||||
const render = renderId && this._renderManagerService.getRenderById(renderId);
|
||||
const spreadsheetRender = render && render.mainComponent as Spreadsheet;
|
||||
if (spreadsheetRender) {
|
||||
list.forEach(([key, Instance]) => {
|
||||
if (!spreadsheetRender.getExtensionByKey(key)) {
|
||||
spreadsheetRender.register(new Instance());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
this.disposeWithMe(this._renderManagerService.currentRender$.subscribe((renderId) => {
|
||||
renderId && register(renderId);
|
||||
}));
|
||||
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
if (workbook) {
|
||||
register(workbook.getUnitId());
|
||||
}
|
||||
}
|
||||
|
||||
_initSkeleton() {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import type { IRange, IScale } from '@univerjs/core';
|
||||
import { Range } from '@univerjs/core';
|
||||
import type { SpreadsheetSkeleton, UniverRenderingContext } from '@univerjs/engine-render';
|
||||
import { FIX_ONE_PIXEL_BLUR_OFFSET, SheetExtension } from '@univerjs/engine-render';
|
||||
import { FIX_ONE_PIXEL_BLUR_OFFSET, SheetExtension, SpreadsheetExtensionRegistry } from '@univerjs/engine-render';
|
||||
import type { IDataBarCellData } from './type';
|
||||
|
||||
export const dataBarUKey = 'sheet-conditional-rule-data-bar';
|
||||
@@ -42,7 +42,7 @@ export class DataBar extends SheetExtension {
|
||||
return false;
|
||||
}
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
// ctx.globalCompositeOperation = 'destination-over';
|
||||
Range.foreach(spreadsheetSkeleton.rowColumnSegment, (row, col) => {
|
||||
const cellData = worksheet.getCell(row, col) as IDataBarCellData;
|
||||
if (cellData && cellData.dataBar) {
|
||||
@@ -150,3 +150,5 @@ export class DataBar extends SheetExtension {
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
SpreadsheetExtensionRegistry.add(DataBar);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import type { IRange, IScale } from '@univerjs/core';
|
||||
import { Range } from '@univerjs/core';
|
||||
import type { SpreadsheetSkeleton, UniverRenderingContext } from '@univerjs/engine-render';
|
||||
import { SheetExtension } from '@univerjs/engine-render';
|
||||
import { SheetExtension, SpreadsheetExtensionRegistry } from '@univerjs/engine-render';
|
||||
import type { IIconType } from '../models/icon-map';
|
||||
import { EMPTY_ICON_TYPE, iconMap } from '../models/icon-map';
|
||||
import type { IIconSetCellData } from './type';
|
||||
@@ -54,7 +54,7 @@ export class ConditionalFormattingIcon extends SheetExtension {
|
||||
return false;
|
||||
}
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
// ctx.globalCompositeOperation = 'destination-over';
|
||||
Range.foreach(spreadsheetSkeleton.rowColumnSegment, (row, col) => {
|
||||
const cellData = worksheet.getCell(row, col) as IIconSetCellData;
|
||||
if (cellData?.iconSet) {
|
||||
@@ -115,3 +115,5 @@ export class ConditionalFormattingIcon extends SheetExtension {
|
||||
return `${iconType}_${iconIndex}`;
|
||||
}
|
||||
}
|
||||
|
||||
SpreadsheetExtensionRegistry.add(ConditionalFormattingIcon);
|
||||
|
||||
@@ -106,8 +106,8 @@ export function getCellIndexByOffsetWithMerge(offsetX: number, offsetY: number,
|
||||
const { scaleX, scaleY } = scene.getAncestorScale();
|
||||
|
||||
const scrollXY = {
|
||||
x: activeViewport.actualScrollX,
|
||||
y: activeViewport.actualScrollY,
|
||||
x: activeViewport.viewportScrollX,
|
||||
y: activeViewport.viewportScrollY,
|
||||
};
|
||||
|
||||
const cellPos = skeleton.getCellPositionByOffset(offsetX, offsetY, scaleX, scaleY, scrollXY);
|
||||
@@ -182,7 +182,7 @@ export function transformPosition2Offset(x: number, y: number, scene: Scene, ske
|
||||
const freezeWidth = endSheetView.startX - startSheetView.startX;
|
||||
const freezeHeight = endSheetView.startY - startSheetView.startY;
|
||||
|
||||
const { top, left, actualScrollX, actualScrollY } = viewMain;
|
||||
const { top, left, viewportScrollX: actualScrollX, viewportScrollY: actualScrollY } = viewMain;
|
||||
let offsetX: number;
|
||||
// viewMain or viewTop
|
||||
if (x > left) {
|
||||
@@ -250,8 +250,8 @@ export function getHoverCellPosition(currentRender: IRender, workbook: Workbook,
|
||||
const { scaleX, scaleY } = scene.getAncestorScale();
|
||||
|
||||
const scrollXY = {
|
||||
x: activeViewport.actualScrollX,
|
||||
y: activeViewport.actualScrollY,
|
||||
x: activeViewport.viewportScrollX,
|
||||
y: activeViewport.viewportScrollY,
|
||||
};
|
||||
|
||||
const position: IPosition = {
|
||||
|
||||
@@ -72,8 +72,8 @@ export class CellCustomRenderController extends Disposable implements IRenderCon
|
||||
const { scaleX, scaleY } = scene.getAncestorScale();
|
||||
|
||||
const scrollXY = {
|
||||
x: activeViewport.actualScrollX,
|
||||
y: activeViewport.actualScrollY,
|
||||
x: activeViewport.viewportScrollX,
|
||||
y: activeViewport.viewportScrollY,
|
||||
};
|
||||
|
||||
const cellPos = skeleton.getCellPositionByOffset(offsetX, offsetY, scaleX, scaleY, scrollXY);
|
||||
|
||||
@@ -90,7 +90,7 @@ const FREEZE_COLUMN_MAIN_NAME = '__SpreadsheetFreezeColumnMainName__';
|
||||
|
||||
const FREEZE_COLUMN_HEADER_NAME = '__SpreadsheetFreezeColumnHeaderName__';
|
||||
|
||||
const FREEZE_SIZE_NORMAL = 4;
|
||||
const FREEZE_SIZE_NORMAL = 2;
|
||||
|
||||
const AUXILIARY_CLICK_HIDDEN_OBJECT_TRANSPARENCY = 0.01;
|
||||
|
||||
@@ -214,9 +214,13 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
|
||||
const scale = Math.max(scene.scaleX, scene.scaleY);
|
||||
|
||||
const FREEZE_SIZE = FREEZE_SIZE_NORMAL / (scale < 1 ? 1 : scale);
|
||||
let FREEZE_SIZE = FREEZE_SIZE_NORMAL / (scale < 1 ? 1 : scale);
|
||||
|
||||
if (freezeDirectionType === FREEZE_DIRECTION_TYPE.ROW) {
|
||||
if (freezeRow === -1 || freezeRow === 0) {
|
||||
FREEZE_SIZE = FREEZE_SIZE * 2;
|
||||
}
|
||||
|
||||
const FREEZE_OFFSET = FREEZE_SIZE;
|
||||
|
||||
this._rowFreezeHeaderRect = new Rect(FREEZE_ROW_HEADER_NAME, {
|
||||
@@ -235,7 +239,7 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
|
||||
this._rowFreezeMainRect = new Rect(FREEZE_ROW_MAIN_NAME, {
|
||||
fill,
|
||||
width: shapeWidth * 2,
|
||||
width: shapeWidth * 2 / scale,
|
||||
height: FREEZE_SIZE,
|
||||
left: rowHeaderWidthAndMarginLeft,
|
||||
top: startY - FREEZE_OFFSET,
|
||||
@@ -244,6 +248,10 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
|
||||
scene.addObjects([this._rowFreezeHeaderRect, this._rowFreezeMainRect], SHEET_COMPONENT_HEADER_LAYER_INDEX);
|
||||
} else {
|
||||
if (freezeColumn === -1 || freezeColumn === 0) {
|
||||
FREEZE_SIZE = FREEZE_SIZE * 2;
|
||||
}
|
||||
|
||||
const FREEZE_OFFSET = FREEZE_SIZE;
|
||||
|
||||
this._columnFreezeHeaderRect = new Rect(FREEZE_COLUMN_HEADER_NAME, {
|
||||
@@ -263,7 +271,7 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
this._columnFreezeMainRect = new Rect(FREEZE_COLUMN_MAIN_NAME, {
|
||||
fill,
|
||||
width: FREEZE_SIZE,
|
||||
height: shapeHeight * 2,
|
||||
height: shapeHeight * 2 / scale,
|
||||
left: startX - FREEZE_OFFSET,
|
||||
top: columnHeaderHeightAndMarginTop,
|
||||
zIndex: 3,
|
||||
@@ -564,7 +572,7 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
const oldFreeze = worksheet.getConfig()?.freeze;
|
||||
let xSplit = oldFreeze?.xSplit || 0;
|
||||
let ySplit = oldFreeze?.ySplit || 0;
|
||||
const viewPortKey = this._activeViewport?.viewPortKey;
|
||||
const viewPortKey = this._activeViewport?.viewportKey;
|
||||
if (freezeDirectionType === FREEZE_DIRECTION_TYPE.ROW) {
|
||||
if (
|
||||
!viewPortKey ||
|
||||
@@ -829,7 +837,7 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
.updateScroll({
|
||||
actualScrollY: startSheetView.startY,
|
||||
x: viewMain.scrollX,
|
||||
actualScrollX: viewMain.actualScrollX,
|
||||
actualScrollX: viewMain.viewportScrollX,
|
||||
});
|
||||
viewRowTop.resize({
|
||||
left: 0,
|
||||
@@ -884,7 +892,7 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
.updateScroll({
|
||||
actualScrollX: startSheetView.startX,
|
||||
y: viewMain.scrollY,
|
||||
actualScrollY: viewMain.actualScrollY,
|
||||
actualScrollY: viewMain.viewportScrollY,
|
||||
});
|
||||
viewColumnLeft.resize({
|
||||
left: rowHeaderWidthAndMarginLeft,
|
||||
@@ -948,7 +956,7 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
.updateScroll({
|
||||
actualScrollX: startSheetView.startX,
|
||||
y: viewMain.scrollY,
|
||||
actualScrollY: viewMain.actualScrollY,
|
||||
actualScrollY: viewMain.viewportScrollY,
|
||||
});
|
||||
viewMainTop.resize({
|
||||
left: rowHeaderWidthAndMarginLeft + leftGap,
|
||||
@@ -960,7 +968,7 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
.updateScroll({
|
||||
actualScrollY: startSheetView.startY,
|
||||
x: viewMain.scrollX,
|
||||
actualScrollX: viewMain.actualScrollX,
|
||||
actualScrollX: viewMain.viewportScrollX,
|
||||
});
|
||||
viewMainLeftTop.resize({
|
||||
left: rowHeaderWidthAndMarginLeft,
|
||||
@@ -1510,6 +1518,15 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
return getSheetObject(this._context.unit, this._context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整冻结 & 缩放都会进入
|
||||
* 但是窗口 resize 并不会进入
|
||||
* @param startRow
|
||||
* @param startColumn
|
||||
* @param ySplit
|
||||
* @param xSplit
|
||||
* @param resetScroll
|
||||
*/
|
||||
private _refreshFreeze(
|
||||
startRow: number,
|
||||
startColumn: number,
|
||||
@@ -1529,7 +1546,11 @@ export class HeaderFreezeRenderController extends Disposable implements IRenderC
|
||||
this._createFreeze(FREEZE_DIRECTION_TYPE.COLUMN, newFreeze);
|
||||
|
||||
this._updateViewport(startRow, startColumn, ySplit, xSplit, resetScroll);
|
||||
|
||||
this._getSheetObject()?.spreadsheet.makeForceDirty();
|
||||
|
||||
// no need to set viewport markForceDirty again
|
||||
// when change freeze area ---> viewport.resize ---> viewport.markForceDity()
|
||||
// this._getSheetObject()?.spreadsheet.makeForceDirty();
|
||||
// this._getSheetObject()?.scene.getViewports().forEach(vp => vp.makeForceDirty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ export class SheetsScrollRenderController extends Disposable implements IRenderC
|
||||
return;
|
||||
}
|
||||
|
||||
// 数据源 packages/sheets-ui/src/services/scroll-manager.service.ts@_addByParam
|
||||
const { sheetViewStartRow, sheetViewStartColumn, offsetX, offsetY } = param;
|
||||
|
||||
const { startX, startY } = skeleton.getCellByIndexWithNoHeader(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import type { ICommandInfo,
|
||||
IRange,
|
||||
Workbook } from '@univerjs/core';
|
||||
import { CommandType,
|
||||
ICommandService,
|
||||
@@ -22,15 +23,18 @@ import { CommandType,
|
||||
IUniverInstanceService,
|
||||
LifecycleStages,
|
||||
OnLifecycle,
|
||||
Rectangle,
|
||||
RxDisposable,
|
||||
toDisposable,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import type { Rect, SpreadsheetColumnHeader, SpreadsheetRowHeader } from '@univerjs/engine-render';
|
||||
import type { IViewportInfos, Rect, Scene, SpreadsheetColumnHeader, SpreadsheetRowHeader, Viewport } from '@univerjs/engine-render';
|
||||
import { IRenderManagerService, RENDER_RAW_FORMULA_KEY, Spreadsheet } from '@univerjs/engine-render';
|
||||
import {
|
||||
COMMAND_LISTENER_SKELETON_CHANGE,
|
||||
COMMAND_LISTENER_VALUE_CHANGE,
|
||||
MoveRangeMutation,
|
||||
SetRangeValuesMutation,
|
||||
SetWorksheetActiveOperation,
|
||||
} from '@univerjs/sheets';
|
||||
import type { IDisposable } from '@wendellhu/redi';
|
||||
@@ -167,6 +171,8 @@ export class SheetRenderController extends RxDisposable {
|
||||
|
||||
// Change the skeleton to render when the sheet is changed.
|
||||
// Should also check the init sheet.
|
||||
// setCurrent ---> currentSkeletonBefore$ ---> zoom.controller.subscribe ---> scene._setTransForm ---> viewports markDirty
|
||||
// setCurrent ---> currentSkeleton$ ---> scroll.controller.subscribe ---> scene?.transformByState ---> scene._setTransFor
|
||||
this._sheetSkeletonManagerService.setCurrent({
|
||||
unitId,
|
||||
sheetId,
|
||||
@@ -177,7 +183,7 @@ export class SheetRenderController extends RxDisposable {
|
||||
}
|
||||
|
||||
if (command.type === CommandType.MUTATION) {
|
||||
this._renderManagerService.getRenderById(unitId)?.mainComponent?.makeDirty(); // refresh spreadsheet
|
||||
this._markSpreadsheetDirty(unitId, command);
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -190,9 +196,99 @@ export class SheetRenderController extends RxDisposable {
|
||||
.subscribe(() => {
|
||||
this._renderManagerService.getRenderAll().forEach((renderer) => {
|
||||
if (renderer.mainComponent instanceof Spreadsheet) {
|
||||
renderer.mainComponent.makeForceDirty(true);
|
||||
(renderer.mainComponent as Spreadsheet).makeForceDirty(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _spreadsheetViewports(scene: Scene) {
|
||||
return scene.getViewports().filter((v) => ['viewMain', 'viewMainLeftTop', 'viewMainTop', 'viewMainLeft'].includes(v.viewportKey));
|
||||
}
|
||||
|
||||
private _markSpreadsheetDirty(unitId: string, command: ICommandInfo) {
|
||||
const currentRender = this._renderManagerService.getRenderById(unitId);
|
||||
if (!currentRender) return;
|
||||
const { mainComponent: spreadsheet, scene } = currentRender;
|
||||
// 现在 spreadsheet.markDirty 会调用 vport.markDirty
|
||||
// 因为其他 controller 中存在 mainComponent?.makeDirty() 的调用, 不止是 sheet-render.controller 在标脏
|
||||
if (spreadsheet) {
|
||||
spreadsheet.makeDirty(); // refresh spreadsheet
|
||||
}
|
||||
scene.makeDirty();
|
||||
if (!command.params) return;
|
||||
const cmdParams = command.params as Record<string, any>;
|
||||
const viewports = this._spreadsheetViewports(scene);
|
||||
if (command.id === SetRangeValuesMutation.id && cmdParams.cellValue) {
|
||||
const dirtyRange: IRange = this._cellValueToRange(cmdParams.cellValue);
|
||||
const dirtyBounds = this._rangeToBounds([dirtyRange]);
|
||||
this._markViewportDirty(viewports, dirtyBounds);
|
||||
(spreadsheet as unknown as Spreadsheet).setDirtyArea(dirtyBounds);
|
||||
}
|
||||
|
||||
if (command.id === MoveRangeMutation.id && cmdParams.from && cmdParams.to) {
|
||||
const fromRange = this._cellValueToRange(cmdParams.from.value);
|
||||
const toRange = this._cellValueToRange(cmdParams.to.value);
|
||||
const dirtyBounds = this._rangeToBounds([fromRange, toRange]);
|
||||
this._markViewportDirty(viewports, dirtyBounds);
|
||||
(spreadsheet as unknown as Spreadsheet).setDirtyArea(dirtyBounds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cellValue data structure:
|
||||
* {[row]: { [col]: value}}
|
||||
* @param cellValue
|
||||
* @returns
|
||||
*/
|
||||
private _cellValueToRange(cellValue: Record<number, Record<number, object>>) {
|
||||
const rows = Object.keys(cellValue).map(Number);
|
||||
const columns = [];
|
||||
|
||||
for (const [_row, columnObj] of Object.entries(cellValue)) {
|
||||
for (const column in columnObj) {
|
||||
columns.push(Number(column));
|
||||
}
|
||||
}
|
||||
|
||||
const startRow = Math.min(...rows);
|
||||
const endRow = Math.max(...rows);
|
||||
const startColumn = Math.min(...columns);
|
||||
const endColumn = Math.max(...columns);
|
||||
|
||||
return {
|
||||
startRow,
|
||||
endRow,
|
||||
startColumn,
|
||||
endColumn,
|
||||
} as IRange;
|
||||
}
|
||||
|
||||
private _rangeToBounds(ranges: IRange[]) {
|
||||
const sk = this._sheetSkeletonManagerService.getCurrent()?.skeleton!;
|
||||
|
||||
const { rowHeightAccumulation, columnWidthAccumulation, rowHeaderWidth, columnHeaderHeight } = sk;
|
||||
// rowHeightAccumulation 已经表示的是行底部的高度
|
||||
const dirtyBounds: IViewportInfos[] = [];
|
||||
for (const r of ranges) {
|
||||
const { startRow, endRow, startColumn, endColumn } = r;
|
||||
const top = startRow === 0 ? 0 : rowHeightAccumulation[startRow - 1] + columnHeaderHeight;
|
||||
const bottom = rowHeightAccumulation[endRow] + columnHeaderHeight;
|
||||
const left = startColumn === 0 ? 0 : columnWidthAccumulation[startColumn - 1] + rowHeaderWidth;
|
||||
const right = columnWidthAccumulation[endColumn] + rowHeaderWidth;
|
||||
dirtyBounds.push({ top, left, bottom, right, width: right - left, height: bottom - top });
|
||||
}
|
||||
return dirtyBounds;
|
||||
}
|
||||
|
||||
private _markViewportDirty(viewports: Viewport[], dirtyBounds: IViewportInfos[]) {
|
||||
const activeViewports = viewports.filter((vp) => vp.isActive && vp.cacheBound);
|
||||
for (const vp of activeViewports) {
|
||||
for (const b of dirtyBounds) {
|
||||
if (Rectangle.hasIntersectionBetweenTwoBounds(vp.cacheBound!, b)) {
|
||||
vp.markDirty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,8 +262,8 @@ export class SheetCanvasPopManagerService extends Disposable {
|
||||
|
||||
const { scaleX, scaleY } = scene.getAncestorScale();
|
||||
const scrollXY = {
|
||||
x: activeViewport.actualScrollX,
|
||||
y: activeViewport.actualScrollY,
|
||||
x: activeViewport.viewportScrollX,
|
||||
y: activeViewport.viewportScrollY,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -808,8 +808,8 @@ export class SelectionRenderService implements ISelectionRenderService {
|
||||
yCrossTime += 1;
|
||||
}
|
||||
|
||||
const startKey = startViewport.viewPortKey;
|
||||
const endKey = endViewport.viewPortKey;
|
||||
const startKey = startViewport.viewportKey;
|
||||
const endKey = endViewport.viewportKey;
|
||||
|
||||
if (startKey === VIEWPORT_KEY.VIEW_ROW_TOP) {
|
||||
if (moveOffsetY < viewportMain.top && (selection?.endRow ?? 0) < (freeze?.startRow ?? 0)) {
|
||||
|
||||
@@ -114,7 +114,7 @@ export class SelectionShapeExtension {
|
||||
ySplit: 0,
|
||||
};
|
||||
|
||||
switch (viewport.viewPortKey) {
|
||||
switch (viewport.viewportKey) {
|
||||
case VIEWPORT_KEY.VIEW_MAIN:
|
||||
return selection.endRow >= freeze.startRow && selection.endColumn >= freeze.startColumn;
|
||||
|
||||
@@ -789,7 +789,7 @@ export class SelectionShapeExtension {
|
||||
|
||||
const scrollTimer = ScrollTimer.create(
|
||||
scene,
|
||||
this._activeViewport.viewPortKey === VIEWPORT_KEY.VIEW_MAIN ? ScrollTimerType.ALL : ScrollTimerType.NONE
|
||||
this._activeViewport.viewportKey === VIEWPORT_KEY.VIEW_MAIN ? ScrollTimerType.ALL : ScrollTimerType.NONE
|
||||
);
|
||||
|
||||
scrollTimer.startScroll(newEvtOffsetX, newEvtOffsetY, viewportMain);
|
||||
@@ -816,7 +816,7 @@ export class SelectionShapeExtension {
|
||||
|
||||
const newSelection = this._targetSelection;
|
||||
|
||||
if (viewportMain && currentViewport && this._activeViewport?.viewPortKey !== currentViewport?.viewPortKey) {
|
||||
if (viewportMain && currentViewport && this._activeViewport?.viewportKey !== currentViewport?.viewportKey) {
|
||||
let movingRange: IRangeWithCoord;
|
||||
if (newSelection.startRow !== originStartRow) {
|
||||
scrollTimer.scrollTimerType = ScrollTimerType.Y;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import type { Workbook, Worksheet } from '@univerjs/core';
|
||||
import { ICommandService, RxDisposable, toDisposable } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderController, IWheelEvent } from '@univerjs/engine-render';
|
||||
import type { IRenderContext, IRenderController, IWheelEvent, Scene } from '@univerjs/engine-render';
|
||||
import {
|
||||
IRenderManagerService,
|
||||
Layer,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
Rect,
|
||||
RENDER_CLASS_TYPE,
|
||||
ScrollBar,
|
||||
SHEET_VIEWPORT_KEY,
|
||||
Spreadsheet,
|
||||
SpreadsheetColumnHeader,
|
||||
SpreadsheetRowHeader,
|
||||
@@ -37,7 +38,6 @@ import {
|
||||
SHEET_COMPONENT_HEADER_LAYER_INDEX,
|
||||
SHEET_COMPONENT_MAIN_LAYER_INDEX,
|
||||
SHEET_VIEW_KEY,
|
||||
VIEWPORT_KEY,
|
||||
} from '../common/keys';
|
||||
import { SheetSkeletonManagerService } from '../services/sheet-skeleton-manager.service';
|
||||
|
||||
@@ -86,9 +86,9 @@ export class SheetCanvasView extends RxDisposable implements IRenderController {
|
||||
|
||||
const sheetId = worksheet.getSheetId();
|
||||
|
||||
this._addViewport(worksheet);
|
||||
|
||||
const spreadsheet = new Spreadsheet(SHEET_VIEW_KEY.MAIN);
|
||||
const _viewMain = this._addViewport(worksheet);
|
||||
|
||||
const spreadsheetRowHeader = new SpreadsheetRowHeader(SHEET_VIEW_KEY.ROW);
|
||||
const spreadsheetColumnHeader = new SpreadsheetColumnHeader(SHEET_VIEW_KEY.COLUMN);
|
||||
const SpreadsheetLeftTopPlaceholder = new Rect(SHEET_VIEW_KEY.LEFT_TOP, {
|
||||
@@ -115,67 +115,129 @@ export class SheetCanvasView extends RxDisposable implements IRenderController {
|
||||
scene.enableLayerCache(SHEET_COMPONENT_MAIN_LAYER_INDEX, SHEET_COMPONENT_HEADER_LAYER_INDEX);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _initViewports(scene: Scene, rowHeader: { width: number }, columnHeader: { height: number }) {
|
||||
const bufferEdgeX = 100;
|
||||
const bufferEdgeY = 100;
|
||||
// window.sc = scene;
|
||||
const viewMain = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN, scene, {
|
||||
left: rowHeader.width,
|
||||
top: columnHeader.height,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isWheelPreventDefaultX: true,
|
||||
isRelativeX: true,
|
||||
isRelativeY: true,
|
||||
allowCache: true,
|
||||
bufferEdgeX,
|
||||
bufferEdgeY,
|
||||
});
|
||||
const viewMainLeftTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT_TOP, scene, {
|
||||
isWheelPreventDefaultX: true,
|
||||
active: false,
|
||||
isRelativeX: false,
|
||||
isRelativeY: false,
|
||||
allowCache: true,
|
||||
bufferEdgeX: 0,
|
||||
bufferEdgeY: 0,
|
||||
});
|
||||
|
||||
const viewMainLeft = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT, scene, {
|
||||
isWheelPreventDefaultX: true,
|
||||
active: false,
|
||||
isRelativeX: false,
|
||||
isRelativeY: true,
|
||||
allowCache: true,
|
||||
bufferEdgeX: 0,
|
||||
bufferEdgeY,
|
||||
});
|
||||
|
||||
const viewMainTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_TOP, scene, {
|
||||
isWheelPreventDefaultX: true,
|
||||
active: false,
|
||||
isRelativeX: true,
|
||||
isRelativeY: false,
|
||||
allowCache: true,
|
||||
bufferEdgeX,
|
||||
bufferEdgeY: 0,
|
||||
});
|
||||
|
||||
const viewRowTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_ROW_TOP, scene, {
|
||||
active: false,
|
||||
isWheelPreventDefaultX: true,
|
||||
isRelativeX: false,
|
||||
isRelativeY: false,
|
||||
});
|
||||
|
||||
const viewRowBottom = new Viewport(SHEET_VIEWPORT_KEY.VIEW_ROW_BOTTOM, scene, {
|
||||
left: 0,
|
||||
top: columnHeader.height,
|
||||
bottom: 0,
|
||||
width: rowHeader.width,
|
||||
isWheelPreventDefaultX: true,
|
||||
isRelativeX: false,
|
||||
isRelativeY: true,
|
||||
});
|
||||
|
||||
const viewColumnLeft = new Viewport(SHEET_VIEWPORT_KEY.VIEW_COLUMN_LEFT, scene, {
|
||||
active: false,
|
||||
isWheelPreventDefaultX: true,
|
||||
isRelativeX: false,
|
||||
isRelativeY: false,
|
||||
});
|
||||
|
||||
const viewColumnRight = new Viewport(SHEET_VIEWPORT_KEY.VIEW_COLUMN_RIGHT, scene, {
|
||||
left: rowHeader.width,
|
||||
top: 0,
|
||||
height: columnHeader.height,
|
||||
right: 0,
|
||||
isWheelPreventDefaultX: true,
|
||||
isRelativeX: true,
|
||||
isRelativeY: false,
|
||||
});
|
||||
|
||||
const viewLeftTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_LEFT_TOP, scene, {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: rowHeader.width,
|
||||
height: columnHeader.height,
|
||||
isWheelPreventDefaultX: true,
|
||||
isRelativeX: false,
|
||||
isRelativeY: false,
|
||||
});
|
||||
|
||||
return {
|
||||
viewMain,
|
||||
viewLeftTop,
|
||||
viewMainLeftTop,
|
||||
viewMainLeft,
|
||||
viewMainTop,
|
||||
viewColumnLeft,
|
||||
viewRowTop,
|
||||
viewRowBottom,
|
||||
viewColumnRight,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* initViewport & wheel event
|
||||
* +-----------------+--------------------+-------------------+
|
||||
* | VIEW_LEFT_TOP | VIEW_COLUMN_LEFT | VIEW_COLUMN_RIGHT |
|
||||
* +-----------------+--------------------+-------------------+
|
||||
* | VIEW_ROW_TOP | VIEW_MAIN_LEFT_TOP | VIEW_MAIN_TOP |
|
||||
* +-----------------+--------------------+-------------------+
|
||||
* | VIEW_ROW_BOTTOM | VIEW_MAIN_LEFT | VIEW_MAIN |
|
||||
* +-----------------+--------------------+-------------------+
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _addViewport(worksheet: Worksheet) {
|
||||
const scene = this._context.scene;
|
||||
|
||||
if (scene == null) {
|
||||
return;
|
||||
}
|
||||
const { rowHeader, columnHeader } = worksheet.getConfig();
|
||||
|
||||
const viewMain = new Viewport(VIEWPORT_KEY.VIEW_MAIN, scene, {
|
||||
left: rowHeader.width,
|
||||
top: columnHeader.height,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_ROW_TOP, scene, {
|
||||
active: false,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_ROW_BOTTOM, scene, {
|
||||
left: 0,
|
||||
top: columnHeader.height,
|
||||
bottom: 0,
|
||||
width: rowHeader.width,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_COLUMN_LEFT, scene, {
|
||||
active: false,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_COLUMN_RIGHT, scene, {
|
||||
left: rowHeader.width,
|
||||
top: 0,
|
||||
height: columnHeader.height,
|
||||
right: 0,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_LEFT_TOP, scene, {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: rowHeader.width,
|
||||
height: columnHeader.height,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_MAIN_LEFT_TOP, scene, {
|
||||
isWheelPreventDefaultX: true,
|
||||
active: false,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_MAIN_LEFT, scene, {
|
||||
isWheelPreventDefaultX: true,
|
||||
active: false,
|
||||
});
|
||||
|
||||
new Viewport(VIEWPORT_KEY.VIEW_MAIN_TOP, scene, {
|
||||
isWheelPreventDefaultX: true,
|
||||
active: false,
|
||||
});
|
||||
const { viewMain } = this._initViewports(scene, rowHeader, columnHeader);
|
||||
|
||||
// mouse scroll
|
||||
this.disposeWithMe(
|
||||
@@ -193,7 +255,8 @@ export class SheetCanvasView extends RxDisposable implements IRenderController {
|
||||
const deltaFactor = Math.abs(evt.deltaX);
|
||||
// let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4;
|
||||
const scrollNum = deltaFactor;
|
||||
|
||||
// 展示更多右侧内容,evt.deltaX > 0
|
||||
// 展示更多左侧内容, evt.deltaX < 0
|
||||
if (evt.deltaX > 0) {
|
||||
offsetX = scrollNum;
|
||||
} else {
|
||||
@@ -263,9 +326,7 @@ export class SheetCanvasView extends RxDisposable implements IRenderController {
|
||||
|
||||
// create a scroll bar
|
||||
new ScrollBar(viewMain);
|
||||
|
||||
scene.attachControl();
|
||||
|
||||
return viewMain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ export class DocsAdaptor extends ObjectAdaptor {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isRelativeX: true,
|
||||
isRelativeY: true,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -130,6 +130,8 @@ export class SlideAdaptor extends ObjectAdaptor {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isRelativeX: true,
|
||||
isRelativeY: true,
|
||||
});
|
||||
|
||||
viewMain.closeClip();
|
||||
|
||||
@@ -172,6 +172,8 @@ export class SpreadsheetAdaptor extends ObjectAdaptor {
|
||||
top: columnHeaderHeightScale,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isRelativeX: true,
|
||||
isRelativeY: true,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
const viewTop = new Viewport(SHEET_VIEW_KEY.VIEW_TOP + id, scene, {
|
||||
@@ -179,6 +181,7 @@ export class SpreadsheetAdaptor extends ObjectAdaptor {
|
||||
height: columnHeaderHeightScale,
|
||||
top: 0,
|
||||
right: 0,
|
||||
isRelativeX: true,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
const viewLeft = new Viewport(SHEET_VIEW_KEY.VIEW_LEFT + id, scene, {
|
||||
@@ -186,6 +189,7 @@ export class SpreadsheetAdaptor extends ObjectAdaptor {
|
||||
bottom: 0,
|
||||
top: columnHeaderHeightScale,
|
||||
width: rowHeaderWidthScale,
|
||||
isRelativeY: true,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ export class CanvasView extends RxDisposable {
|
||||
return this._renderManagerService.getRenderById(slideDataModel.getUnitId());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _addNewRender(unitId: string) {
|
||||
const slideDataModel = this._univerInstanceService.getUnit<SlideDataModel>(unitId, UniverInstanceType.UNIVER_SLIDE);
|
||||
|
||||
@@ -175,6 +176,8 @@ export class CanvasView extends RxDisposable {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isRelativeX: true,
|
||||
isRelativeY: true,
|
||||
isWheelPreventDefaultX: true,
|
||||
});
|
||||
|
||||
@@ -412,6 +415,8 @@ export class CanvasView extends RxDisposable {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
isRelativeX: true,
|
||||
isRelativeY: true,
|
||||
});
|
||||
|
||||
viewMain.closeClip();
|
||||
|
||||
Generated
+3
-13
@@ -764,6 +764,9 @@ importers:
|
||||
'@wendellhu/redi':
|
||||
specifier: 0.15.2
|
||||
version: 0.15.2
|
||||
jest-canvas-mock:
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2
|
||||
jsdom:
|
||||
specifier: ^24.0.0
|
||||
version: 24.0.0
|
||||
@@ -779,9 +782,6 @@ importers:
|
||||
vitest:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(@types/node@20.12.12)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)
|
||||
vitest-canvas-mock:
|
||||
specifier: ^0.3.3
|
||||
version: 0.3.3(vitest@1.6.0(@types/node@20.12.12)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0))
|
||||
|
||||
packages/find-replace:
|
||||
dependencies:
|
||||
@@ -9922,11 +9922,6 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
|
||||
vitest-canvas-mock@0.3.3:
|
||||
resolution: {integrity: sha512-3P968tYBpqYyzzOaVtqnmYjqbe13576/fkjbDEJSfQAkHtC5/UjuRHOhFEN/ZV5HVZIkaROBUWgazDKJ+Ibw+Q==}
|
||||
peerDependencies:
|
||||
vitest: '*'
|
||||
|
||||
vitest@1.6.0:
|
||||
resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -19489,11 +19484,6 @@ snapshots:
|
||||
sass: 1.72.0
|
||||
terser: 5.30.0
|
||||
|
||||
vitest-canvas-mock@0.3.3(vitest@1.6.0(@types/node@20.12.12)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)):
|
||||
dependencies:
|
||||
jest-canvas-mock: 2.5.2
|
||||
vitest: 1.6.0(@types/node@20.12.12)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)
|
||||
|
||||
vitest@1.6.0(@types/node@20.12.12)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 1.6.0
|
||||
|
||||
Reference in New Issue
Block a user