Skip to content

Commit

Permalink
feat(ui): add switch render unit feature
Browse files Browse the repository at this point in the history
  • Loading branch information
wzhudev committed Feb 5, 2025
1 parent ff4ae42 commit 7766970
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 59 deletions.
2 changes: 1 addition & 1 deletion mockdata/src/sheets/demo/default-workbook-data-demo4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const richTextDemo1: IDocumentData = {
};

export const DEFAULT_WORKBOOK_DATA_DEMO4: IWorkbookData = {
id: 'workbook-01',
id: 'workbook-04',
locale: LocaleType.ZH_CN,
name: 'universheet',
sheetOrder: ['sheet-0004'],
Expand Down
58 changes: 47 additions & 11 deletions packages/engine-render/src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@
* limitations under the License.
*/

import type { Nullable } from '@univerjs/core';
import type { IDisposable, Nullable } from '@univerjs/core';

import type { CURSOR_TYPE } from './basics/const';
import type { IEvent, IKeyboardEvent, IPointerEvent } from './basics/i-events';
import type { ITimeMetric, ITransformChangeState } from './basics/interfaces';
import type { IBasicFrameInfo } from './basics/performance-monitor';
import type { Scene } from './scene';
import { Disposable, EventSubject, toDisposable, Tools } from '@univerjs/core';
import { Observable, shareReplay, Subject } from 'rxjs';
import { type CURSOR_TYPE, RENDER_CLASS_TYPE } from './basics/const';
import { RENDER_CLASS_TYPE } from './basics/const';
import { DeviceType, PointerInput } from './basics/i-events';
import { TRANSFORM_CHANGE_OBSERVABLE_TYPE } from './basics/interfaces';
import { PerformanceMonitor } from './basics/performance-monitor';
Expand Down Expand Up @@ -298,20 +299,50 @@ export class Engine extends Disposable {
return this.getCanvas().getPixelRatio();
}

setContainer(elem: HTMLElement, resize = true) {
if (this._container === elem) {
private _resizeListenerDisposable: IDisposable | undefined;

/**
* Mount the canvas to the element so it would be rendered on UI.
* @param {HTMLElement} element - The element the canvas will mount on.
* @param {true} [resize] If should perform resize when mounted and observe resize event.
*/
mount(element: HTMLElement, resize = true): void {
this.setContainer(element, resize);
}

/**
* Unmount the canvas without disposing it so it can be mounted again.
*/
unmount(): void {
this._clearResizeListener();

if (!this._container) {
throw new Error('[Engine]: cannot unmount when container is not set!');
}

this._container.removeChild(this.getCanvasElement());
this._container = null;
}

/**
* Mount the canvas to the element so it would be rendered on UI.
* @deprecated Please use `mount` instead.
* @param {HTMLElement} element - The element the canvas will mount on.
* @param {true} [resize] If should perform resize when mounted and observe resize event.
*/
setContainer(element: HTMLElement, resize = true) {
if (this._container === element) {
return;
}

this._container = elem;
this._container = element;
this._container.appendChild(this.getCanvasElement());

this._clearResizeListener();

if (resize) {
this.resize();

this._resizeObserver?.unobserve(this._container as HTMLElement);
this._resizeObserver = null;

let timer: number | undefined;
this._resizeObserver = new ResizeObserver(() => {
if (!timer) {
Expand All @@ -323,13 +354,18 @@ export class Engine extends Disposable {
});
this._resizeObserver.observe(this._container);

this.disposeWithMe(() => {
this._resizeObserver?.unobserve(this._container as HTMLElement);
this._resizeListenerDisposable = toDisposable(() => {
this._resizeObserver!.unobserve(this._container as HTMLElement);
if (timer !== undefined) window.cancelIdleCallback(timer);
});
}
}

private _clearResizeListener(): void {
this._resizeListenerDisposable?.dispose();
this._resizeListenerDisposable = undefined;
}

resize() {
if (!this._container) {
return;
Expand Down Expand Up @@ -408,7 +444,7 @@ export class Engine extends Disposable {
this._beginFrame$.complete();
this._endFrame$.complete();

this._resizeObserver?.disconnect();
this._clearResizeListener();
this._container = null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@ import type { DocComponent } from '../components/docs/doc-component';

import type { SheetComponent } from '../components/sheets/sheet-component';
import type { Slide } from '../components/slides/slide';
import type { IRender } from './render-unit';
import { createIdentifier, Disposable, Inject, Injector, IUniverInstanceService, remove, toDisposable, UniverInstanceType } from '@univerjs/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { Engine } from '../engine';
import { Scene } from '../scene';
import { type IRender, RenderUnit } from './render-unit';
import { RenderUnit } from './render-unit';

export type RenderComponentType = SheetComponent | DocComponent | Slide | BaseObject;

export interface IRenderManagerService extends IDisposable {
/** @deprecated */
currentRender$: Observable<Nullable<string>>;
getCurrent(): Nullable<IRender>;

addRender(unitId: string, renderer: IRender): void;

Expand Down Expand Up @@ -71,8 +72,6 @@ export interface IRenderManagerService extends IDisposable {
created$: Observable<IRender>;
disposed$: Observable<string>;

/** @deprecated There will be multi units to render at the same time, so there is no *current*. */
getCurrent(): Nullable<IRender>;
/** @deprecated There will be multi units to render at the same time, so there is no *first*. */
getFirst(): Nullable<IRender>;

Expand Down Expand Up @@ -332,7 +331,6 @@ export class RenderManagerService extends Disposable implements IRenderManagerSe

setCurrent(unitId: string): void {
this._currentUnitId = unitId;

this._currentRender$.next(unitId);
}

Expand Down
36 changes: 36 additions & 0 deletions packages/engine-render/src/render-manager/render-unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
*/

import type { Dependency, DependencyIdentifier, IDisposable, Nullable, UnitModel, UnitType, UniverInstanceType } from '@univerjs/core';
import type { Observable } from 'rxjs';
import type { Engine } from '../engine';
import type { Scene } from '../scene';
import type { RenderComponentType } from './render-manager.service';
import { Disposable, Inject, Injector, isClassDependencyItem } from '@univerjs/core';
import { BehaviorSubject, distinctUntilChanged } from 'rxjs';

/**
* Public interface of a {@link RenderUnit}.
Expand All @@ -35,8 +37,24 @@ export interface IRender {
isMainScene: boolean;
isThumbNail?: boolean;

/**
* Whether the render unit is activated. It should emit value when subscribed immediately.
* When created, the render unit is activated by default.
*/
activated$: Observable<boolean>;

with<T>(dependency: DependencyIdentifier<T>): T;
getRenderContext?(): IRenderContext;
/**
* Deactivate the render unit, means the render unit would be freezed and not updated,
* even removed from the webpage. However, the render unit is still in the memory and
* could be activated again.
*/
deactivate(): void;
/**
* Activate the render unit, means the render unit would be updated and rendered.
*/
activate(): void;
}

/**
Expand All @@ -60,6 +78,9 @@ export interface IRenderContext<T extends UnitModel = UnitModel> extends Omit<IR
export class RenderUnit extends Disposable implements IRender {
readonly isRenderUnit: boolean = true;

private readonly _activated$ = new BehaviorSubject<boolean>(true);
readonly activated$ = this._activated$.pipe(distinctUntilChanged());

get unitId(): string { return this._renderContext.unitId; }
get type(): UnitType { return this._renderContext.type; }

Expand Down Expand Up @@ -94,12 +115,19 @@ export class RenderUnit extends Disposable implements IRender {
isMainScene: init.isMainScene,
engine: init.engine,
scene: init.scene,
activated$: this.activated$,
activate: () => this._activated$.next(true),
deactivate: () => this._activated$.next(false),
};
}

override dispose(): void {
this._injector.dispose();

super.dispose();

this._activated$.next(false);
this._activated$.complete();
}

/**
Expand Down Expand Up @@ -145,4 +173,12 @@ export class RenderUnit extends Disposable implements IRender {
getRenderContext(): IRenderContext {
return this._renderContext;
}

activate(): void {
this._renderContext.activate();
}

deactivate(): void {
this._renderContext.deactivate();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
*/

import type { ICommandInfo, IDrawingParam, IMutationInfo, IRange, ITransformState, Nullable, Workbook } from '@univerjs/core';
import type { IDrawingJsonUndo1 } from '@univerjs/drawing';
import type { IRenderContext, IRenderModule } from '@univerjs/engine-render';
import type { IInsertColCommandParams, IInsertRowCommandParams, IMoveColsCommandParams, IMoveRangeCommandParams, IMoveRowsCommandParams, IRemoveRowColCommandParams, ISetColHiddenMutationParams, ISetColVisibleMutationParams, ISetRowHiddenMutationParams, ISetRowVisibleMutationParams, ISetSpecificColsVisibleCommandParams, ISetSpecificRowsVisibleCommandParams, ISetWorksheetActiveOperationParams, ISetWorksheetColWidthMutationParams, ISetWorksheetRowHeightMutationParams, ISetWorksheetRowIsAutoHeightMutationParams } from '@univerjs/sheets';
import type { ISheetDrawing, ISheetDrawingPosition } from '@univerjs/sheets-drawing';
import { Disposable, ICommandService, Inject, IUniverInstanceService, Rectangle } from '@univerjs/core';
import { type IDrawingJsonUndo1, IDrawingManagerService } from '@univerjs/drawing';
import { type IRenderContext, IRenderManagerService, type IRenderModule } from '@univerjs/engine-render';
import { IDrawingManagerService } from '@univerjs/drawing';
import { IRenderManagerService } from '@univerjs/engine-render';
import { DeleteRangeMoveLeftCommand, DeleteRangeMoveUpCommand, DeltaColumnWidthCommand, DeltaRowHeightCommand, getSheetCommandTarget, InsertColCommand, InsertRangeMoveDownCommand, InsertRangeMoveRightCommand, InsertRowCommand, MoveColsCommand, MoveRangeCommand, MoveRowsCommand, RemoveColCommand, RemoveRowCommand, SetColHiddenCommand, SetColHiddenMutation, SetColVisibleMutation, SetColWidthCommand, SetRowHeightCommand, SetRowHiddenCommand, SetRowHiddenMutation, SetRowVisibleMutation, SetSpecificColsVisibleCommand, SetSpecificRowsVisibleCommand, SetWorksheetActiveOperation, SetWorksheetColWidthMutation, SetWorksheetRowHeightMutation, SheetInterceptorService } from '@univerjs/sheets';
import { DrawingApplyType, ISheetDrawingService, SetDrawingApplyMutation, SheetDrawingAnchorType } from '@univerjs/sheets-drawing';
import { attachRangeWithCoord, ISheetSelectionRenderService, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
Expand Down Expand Up @@ -1107,46 +1109,93 @@ export class SheetDrawingTransformAffectedController extends Disposable implemen

private _commandListener() {
this.disposeWithMe(
// TODO@weird94: this should subscribe to the command service
// but the skeleton changes like other render modules. These two signals are not equivalent.
// As a temp solution, I subscribed to activate$ here.
this._commandService.onCommandExecuted((command: ICommandInfo) => {
if (command.id === SetWorksheetActiveOperation.id) {
setTimeout(() => {
const params = command.params as ISetWorksheetActiveOperationParams;
const { unitId: showUnitId, subUnitId: showSubunitId } = params;
const { unitId, subUnitId } = command.params as ISetWorksheetActiveOperationParams;
this._updateDrawings(unitId, subUnitId);
}
})
);

const drawingMap = this._drawingManagerService.drawingManagerData;
this.disposeWithMe(
this._context.activated$.subscribe((activated) => {
const { unit, unitId } = this._context;
if (activated) {
const subUnitId = unit.getActiveSheet().getSheetId();
this._updateDrawings(unitId, subUnitId);
} else {
// Better, dispose the command service listener here.
this._clearDrawings(unitId);
}
})
);
}

const insertDrawings: IDrawingParam[] = [];
private _clearDrawings(selfUnitId: string): void {
setTimeout(() => {
const drawingMap = this._drawingManagerService.drawingManagerData;
const removeDrawings: IDrawingParam[] = [];

const removeDrawings: IDrawingParam[] = [];
// TODO@weird94: should add a iterating function
Object.keys(drawingMap).forEach((unitId) => {
const subUnitMap = drawingMap[unitId];
if (subUnitMap == null) {
return;
}

Object.keys(drawingMap).forEach((unitId) => {
const subUnitMap = drawingMap[unitId];
if (subUnitMap == null) {
return;
}
Object.keys(subUnitMap).forEach((subUnitId) => {
const drawingData = subUnitMap[subUnitId].data;
if (drawingData == null) {
return;
}
Object.keys(drawingData).forEach((drawingId) => {
if (unitId === showUnitId && subUnitId === showSubunitId) {
const drawing = drawingData[drawingId] as ISheetDrawing;
drawing.transform = drawingPositionToTransform(drawing.sheetTransform, this._selectionRenderService, this._skeletonManagerService);
insertDrawings.push(drawingData[drawingId]);
} else {
removeDrawings.push(drawingData[drawingId]);
}
});
});
});
Object.keys(subUnitMap).forEach((subUnitId) => {
const drawingData = subUnitMap[subUnitId].data;
if (drawingData == null) {
return;
}

this._drawingManagerService.removeNotification(removeDrawings);
this._drawingManagerService.addNotification(insertDrawings);
}, 0);
Object.keys(drawingData).forEach((drawingId) => {
if (unitId === selfUnitId) {
removeDrawings.push(drawingData[drawingId]);
}
});
});
});

this._drawingManagerService.removeNotification(removeDrawings);
});
}

private _updateDrawings(showUnitId: string, showSubunitId: string): void {
// TODO@weird94: remove the setTimeout here
setTimeout(() => {
const drawingMap = this._drawingManagerService.drawingManagerData;
const insertDrawings: IDrawingParam[] = [];
const removeDrawings: IDrawingParam[] = [];

Object.keys(drawingMap).forEach((unitId) => {
const subUnitMap = drawingMap[unitId];
if (subUnitMap == null) {
return;
}
})
);
Object.keys(subUnitMap).forEach((subUnitId) => {
const drawingData = subUnitMap[subUnitId].data;
if (drawingData == null) {
return;
}
Object.keys(drawingData).forEach((drawingId) => {
if (unitId === showUnitId && subUnitId === showSubunitId) {
const drawing = drawingData[drawingId] as ISheetDrawing;
drawing.transform = drawingPositionToTransform(drawing.sheetTransform, this._selectionRenderService, this._skeletonManagerService);
insertDrawings.push(drawingData[drawingId]);
} else {
removeDrawings.push(drawingData[drawingId]);
}
});
});
});

this._drawingManagerService.removeNotification(removeDrawings);
this._drawingManagerService.addNotification(insertDrawings);
}, 0);
}

private _sheetRefreshListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,16 @@ export class SheetRenderController extends RxDisposable implements IRenderModule
const sheetId = worksheet.getSheetId();
this._sheetSkeletonManagerService.setCurrent({ sheetId });

// TODO: we should attach the context object to the RenderContext object on scene.canvas.
engine.runRenderLoop(() => scene.render());
const frameFn = () => scene.render();
this.disposeWithMe(this._context.activated$.subscribe((activated) => {
if (activated) {
// TODO: we should attach the context object to the RenderContext object on scene.canvas.
engine.runRenderLoop(frameFn);
} else {
// Stop the render loop when the render unit is deactivated.
engine.stopRenderLoop(frameFn);
}
}));
}

private _renderFrameTimeMetric: Nullable<Record<string, number[]>> = null;
Expand Down
Loading

0 comments on commit 7766970

Please sign in to comment.