Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: customize row header #2457

Merged
merged 7 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,83 @@ import { getColor } from '../../../basics/tools';
import type { UniverRenderingContext } from '../../../context';
import { SheetRowHeaderExtensionRegistry } from '../../extension';
import type { SpreadsheetSkeleton } from '../sheet-skeleton';
import type { IARowCfg, IARowCfgObj, IColumnStyleCfg, IRowStyleCfg } from '../interfaces.ts';
import { SheetExtension } from './sheet-extension';

const UNIQUE_KEY = 'DefaultRowHeaderLayoutExtension';

export interface IRowsHeaderCfgParam {
headerStyle?: Partial<IRowStyleCfg>;
rowsCfg?: IARowCfg[];
}

const DEFAULT_ROW_STYLE = {
fontSize: 13,
fontFamily: DEFAULT_FONTFACE_PLANE,
fontColor: '#000000',
backgroundColor: getColor([248, 249, 250]),
borderColor: getColor([217, 217, 217]),
textAlign: 'center',
textBaseline: 'middle',
} as const;

export class RowHeaderLayout extends SheetExtension {
override uKey = UNIQUE_KEY;

override Z_INDEX = 10;
rowsCfg: IARowCfg[] = [];
headerStyle: IRowStyleCfg = {
fontSize: DEFAULT_ROW_STYLE.fontSize,
fontFamily: DEFAULT_ROW_STYLE.fontFamily,
fontColor: DEFAULT_ROW_STYLE.fontColor,
backgroundColor: DEFAULT_ROW_STYLE.backgroundColor,
borderColor: DEFAULT_ROW_STYLE.borderColor,
textAlign: DEFAULT_ROW_STYLE.textAlign,
textBaseline: DEFAULT_ROW_STYLE.textBaseline,
};

constructor(cfg?: IRowsHeaderCfgParam) {
super();
if (cfg) {
this.configHeaderRow(cfg);
}
}

configHeaderRow(cfg: IRowsHeaderCfgParam) {
this.rowsCfg = cfg.rowsCfg || [];
this.headerStyle = { ...this.headerStyle, ...cfg.headerStyle };
}

getCfgOfCurrentRow(rowIndex: number) {
let mergeWithSpecCfg;
let curRowSpecCfg;
const rowsCfg = this.rowsCfg || [];

if (rowsCfg[rowIndex]) {
if (typeof rowsCfg[rowIndex] == 'string') {
rowsCfg[rowIndex] = { text: rowsCfg[rowIndex] } as IARowCfgObj;
}
curRowSpecCfg = rowsCfg[rowIndex] as IRowStyleCfg & { text: string };
mergeWithSpecCfg = { ...this.headerStyle, ...curRowSpecCfg };
} else {
mergeWithSpecCfg = { ...this.headerStyle, text: `${rowIndex + 1}` };
}
const specStyle = Object.keys(curRowSpecCfg || {}).length > 1; // if cfg have more keys than 'text', means there would be special style config for this row.
return [mergeWithSpecCfg, specStyle] as [IARowCfgObj, boolean];
}

setStyleToCtx(ctx: UniverRenderingContext, rowStyle: Partial<IColumnStyleCfg>) {
if (rowStyle.textAlign) ctx.textAlign = rowStyle.textAlign;
if (rowStyle.textBaseline) ctx.textBaseline = rowStyle.textBaseline;
if (rowStyle.fontColor) ctx.fillStyle = rowStyle.fontColor;
if (rowStyle.borderColor) ctx.strokeStyle = rowStyle.borderColor;
if (rowStyle.fontSize) ctx.font = `${rowStyle.fontSize}px ${DEFAULT_FONTFACE_PLANE}`;
}

// eslint-disable-next-line max-lines-per-function
override draw(ctx: UniverRenderingContext, parentScale: IScale, spreadsheetSkeleton: SpreadsheetSkeleton) {
const { rowColumnSegment, rowHeaderWidth = 0 } = spreadsheetSkeleton;
const { startRow, endRow } = rowColumnSegment;

if (!spreadsheetSkeleton || rowHeaderWidth === 0) {
return;
}
Expand All @@ -50,19 +115,17 @@ export class RowHeaderLayout extends SheetExtension {
}

const scale = this._getScale(parentScale);
this.setStyleToCtx(ctx, this.headerStyle);

ctx.fillStyle = getColor([248, 249, 250])!;
// background
ctx.save();
ctx.fillStyle = this.headerStyle.backgroundColor;
ctx.fillRectByPrecision(0, 0, rowHeaderWidth, rowTotalHeight);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = getColor([0, 0, 0])!;
ctx.beginPath();
ctx.setLineWidthByPrecision(1);
ctx.restore();

ctx.setLineWidthByPrecision(1);
ctx.translateWithPrecisionRatio(FIX_ONE_PIXEL_BLUR_OFFSET, FIX_ONE_PIXEL_BLUR_OFFSET);

ctx.strokeStyle = getColor([217, 217, 217])!;
ctx.font = `13px ${DEFAULT_FONTFACE_PLANE}`;
let preRowPosition = 0;
const rowHeightAccumulationLength = rowHeightAccumulation.length;
for (let r = startRow - 1; r <= endRow; r++) {
Expand All @@ -71,21 +134,66 @@ export class RowHeaderLayout extends SheetExtension {
}
const rowEndPosition = rowHeightAccumulation[r];
if (preRowPosition === rowEndPosition) {
// Skip hidden rows
continue;
continue; // Skip hidden rows
}
const cellBound = {
left: 0,
top: preRowPosition,
right: rowHeaderWidth,
bottom: rowEndPosition,
width: rowHeaderWidth,
height: rowEndPosition - preRowPosition,
};
const [curRowCfg, specStyle] = this.getCfgOfCurrentRow(r);

// background
if (specStyle && curRowCfg.backgroundColor) {
ctx.save();
ctx.fillStyle = curRowCfg.backgroundColor;
ctx.fillRectByPrecision(cellBound.left, cellBound.top, cellBound.width, cellBound.height);
ctx.restore();
}

// horizontal line border
ctx.beginPath();
ctx.moveToByPrecision(cellBound.left, cellBound.bottom);
ctx.lineToByPrecision(cellBound.right, cellBound.bottom);
ctx.stroke();

// row header text
const textX = (() => {
switch (curRowCfg.textAlign) {
case 'center':
return cellBound.left + (cellBound.right - cellBound.left) / 2;
case 'right':
return cellBound.right - MIDDLE_CELL_POS_MAGIC_NUMBER;
case 'left':
return cellBound.left + MIDDLE_CELL_POS_MAGIC_NUMBER;
default: // center
return cellBound.left + (cellBound.right - cellBound.left) / 2;
}
})();
const middleYCellRect = preRowPosition + (rowEndPosition - preRowPosition) / 2 + MIDDLE_CELL_POS_MAGIC_NUMBER; // Magic number 1, because the vertical alignment appears to be off by 1 pixel

if (specStyle) {
ctx.save();
ctx.beginPath();
this.setStyleToCtx(ctx, curRowCfg);
ctx.rectByPrecision(cellBound.left, cellBound.top, cellBound.width, cellBound.height);
ctx.clip();
}

ctx.fillText(curRowCfg.text, textX, middleYCellRect);
if (specStyle) {
ctx.restore();
}
ctx.moveToByPrecision(0, rowEndPosition);
ctx.lineToByPrecision(rowHeaderWidth, rowEndPosition);

const middleCellPos = preRowPosition + (rowEndPosition - preRowPosition) / 2;
ctx.fillText(`${r + 1}`, rowHeaderWidth / 2, middleCellPos + MIDDLE_CELL_POS_MAGIC_NUMBER); // Magic number 1, because the vertical alignment appears to be off by 1 pixel.
preRowPosition = rowEndPosition;
}
// console.log('xx2', rowColumnIndexRange, bounds, this._rowTotalHeight, this._rowHeightAccumulation);

// painting line bottom border
// border right line
const rowHeaderWidthFix = rowHeaderWidth - 0.5 / scale;

ctx.beginPath();
ctx.moveToByPrecision(rowHeaderWidthFix, 0);
ctx.lineToByPrecision(rowHeaderWidthFix, rowTotalHeight);
ctx.stroke();
Expand Down
17 changes: 14 additions & 3 deletions packages/engine-render/src/components/sheets/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ import type { DocumentSkeleton } from '../docs/layout/doc-skeleton';
import type { Canvas } from '../../canvas';
import type { UniverRenderingContext } from '../../context';

export
interface BorderCache {
export interface BorderCache {
[key: string]: BorderCacheItem | {};
}

Expand Down Expand Up @@ -108,7 +107,6 @@ export interface IPaintForScrolling {
scaleX: number;
scaleY: number;
}

export interface IColumnStyleCfg {
fontFamily: string;
fontColor: string;
Expand All @@ -121,3 +119,16 @@ export interface IColumnStyleCfg {

export type IAColumnCfgObj = IColumnStyleCfg & { text: string };
export type IAColumnCfg = undefined | null | string | Partial<IAColumnCfgObj>;

export interface IRowStyleCfg {
fontFamily: string;
fontColor: string;
fontSize: number;
borderColor: string;
textAlign: CanvasTextAlign;
textBaseline: CanvasTextBaseline;
backgroundColor: string;
}

export type IARowCfgObj = IColumnStyleCfg & { text: string };
export type IARowCfg = undefined | null | string | Partial<IARowCfgObj>;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { Nullable } from '@univerjs/core';
import type { IViewportInfo, Vector2 } from '../../basics/vector2';
import type { UniverRenderingContext } from '../../context';
import { SheetRowHeaderExtensionRegistry } from '../extension';
import type { RowHeaderLayout } from './extensions/row-header-layout';
import type { IRowsHeaderCfgParam, RowHeaderLayout } from './extensions/row-header-layout';
import { SpreadsheetHeader } from './sheet-component';
import type { SpreadsheetSkeleton } from './sheet-skeleton';

Expand Down Expand Up @@ -97,4 +97,9 @@ export class SpreadsheetRowHeader extends SpreadsheetHeader {
});
this._rowHeaderLayoutExtension = this.getExtensionByKey('DefaultRowHeaderLayoutExtension') as RowHeaderLayout;
}

setCustomHeader(cfg: IRowsHeaderCfgParam) {
this.makeDirty(true);
this._rowHeaderLayoutExtension.configHeaderRow(cfg);
}
}
31 changes: 29 additions & 2 deletions packages/facade/src/apis/__tests__/facade.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ import { ICommandService, IUniverInstanceService } from '@univerjs/core';
import { SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand } from '@univerjs/sheets';
import type { Injector } from '@wendellhu/redi';

import type { ColumnHeaderLayout, RenderComponentType, SheetComponent, SpreadsheetColumnHeader } from '@univerjs/engine-render';
import { IRenderManagerService } from '@univerjs/engine-render';
import type {
ColumnHeaderLayout,
RenderComponentType,
RowHeaderLayout,
SheetComponent,
SpreadsheetColumnHeader,
SpreadsheetRowHeader } from '@univerjs/engine-render';
import {
IRenderManagerService } from '@univerjs/engine-render';
import { SHEET_VIEW_KEY } from '@univerjs/sheets-ui';
import { RegisterFunctionMutation, SetFormulaCalculationStartMutation, UnregisterFunctionMutation } from '@univerjs/engine-formula';
import { IDescriptionService } from '@univerjs/sheets-formula';
Expand Down Expand Up @@ -294,6 +301,7 @@ describe('Test FUniver', () => {

const spy = vi.spyOn(columnHeaderExt, 'draw');

univerAPI.customizeColumnHeader({ headerStyle: { backgroundColor: 'pink', fontSize: 9 }, columnsCfg: ['ASC', 'MokaII', undefined, { text: 'Size', textAlign: 'left' }, { text: 'MUJI', fontSize: 15, textAlign: 'right' }, { text: 'SRI-RESOLVE', fontSize: 10, textAlign: 'left', fontColor: 'blue', backgroundColor: 'wheat' }, null, null, 'ss', { fontSize: 29, fontColor: 'red', text: 'hash' }] });
univerAPI.customizeColumnHeader({ headerStyle: { backgroundColor: 'pink', fontSize: 9 }, columnsCfg: ['ASC', 'MokaII', undefined, { text: 'Size', textAlign: 'left' }, { text: 'MUJI', fontSize: 15, textAlign: 'right' }, { text: 'SRI-RESOLVE', fontSize: 10, textAlign: 'left', fontColor: 'blue', backgroundColor: 'wheat' }, null, null, 'ss', { fontSize: 29, fontColor: 'red', text: 'hash' }] });
expect(columnHeaderExt.headerStyle.backgroundColor).toBe('pink');
expect(columnHeaderExt.headerStyle.fontSize).toBe(9);
Expand All @@ -303,4 +311,23 @@ describe('Test FUniver', () => {
vi.advanceTimersByTime(16); // mock time pass by
expect(spy).toHaveBeenCalled();
});

it('Function customizeRowHeader', () => {
const unitId = univerAPI.getActiveWorkbook()?.getId() || '';
const rowRenderComp = getSheetRenderComponent(unitId, SHEET_VIEW_KEY.ROW) as SpreadsheetRowHeader;
if (!rowRenderComp) return;
const rowHeaderExt = rowRenderComp.extensions.get('DefaultRowHeaderLayoutExtension')! as RowHeaderLayout;

const spy = vi.spyOn(rowHeaderExt, 'draw');

univerAPI.customizeRowHeader({ headerStyle: { backgroundColor: 'pink', fontSize: 9 }, rowsCfg: ['ASC', 'MokaII', undefined, { text: 'Size', textAlign: 'left' }, { text: 'MUJI', fontSize: 15, textAlign: 'right' }, { text: 'SRI-RESOLVE', fontSize: 10, textAlign: 'left', fontColor: 'blue', backgroundColor: 'wheat' }, null, null, 'ss', { fontSize: 29, fontColor: 'red', text: 'hash' }] });
univerAPI.customizeRowHeader({ headerStyle: { backgroundColor: 'pink', fontSize: 9 }, rowsCfg: ['ASC', 'MokaII', undefined, { text: 'Size', textAlign: 'left' }, { text: 'MUJI', fontSize: 15, textAlign: 'right' }, { text: 'SRI-RESOLVE', fontSize: 10, textAlign: 'left', fontColor: 'blue', backgroundColor: 'wheat' }, null, null, 'ss', { fontSize: 29, fontColor: 'red', text: 'hash' }] });
expect(rowHeaderExt.headerStyle.backgroundColor).toBe('pink');
expect(rowHeaderExt.headerStyle.fontSize).toBe(9);
expect(rowHeaderExt.headerStyle.borderColor).toBe('rgb(217,217,217)');
expect(rowHeaderExt.rowsCfg.length).toBe(10);

vi.advanceTimersByTime(16); // mock time pass by
expect(spy).toHaveBeenCalled();
});
});
24 changes: 21 additions & 3 deletions packages/facade/src/apis/facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@ import { IRegisterFunctionService, RegisterFunctionService } from '@univerjs/she
import type { Dependency, IDisposable } from '@wendellhu/redi';
import { Inject, Injector, Quantity } from '@wendellhu/redi';

import type { IColumnsHeaderCfgParam, RenderComponentType, SheetComponent, SheetExtension, SpreadsheetColumnHeader } from '@univerjs/engine-render';
import { IRenderManagerService } from '@univerjs/engine-render';
import type {
IColumnsHeaderCfgParam,
IRowsHeaderCfgParam,
RenderComponentType,
SheetComponent,
SheetExtension, SpreadsheetColumnHeader,
SpreadsheetRowHeader } from '@univerjs/engine-render';
import {
IRenderManagerService } from '@univerjs/engine-render';
import { SHEET_VIEW_KEY } from '@univerjs/sheets-ui';
import { SetFormulaCalculationStartMutation } from '@univerjs/engine-formula';
import { FDocument } from './docs/f-document';
Expand Down Expand Up @@ -360,7 +367,7 @@ export class FUniver {
* customizeColumnHeader
* @param cfg
* cfg example
({ headerStyle:{backgroundColor: 'pink', fontSize: 9}, columnsCfg: ['MokaII', undefined, null, {text: 'Size', textAlign: 'left'}]})
({ headerStyle:{backgroundColor: 'pink', fontSize: 9}, columnsCfg: ['MokaII', undefined, null, {text: 'Size', textAlign: 'left'}]})
*/
customizeColumnHeader(cfg: IColumnsHeaderCfgParam) {
const wb = this.getActiveWorkbook();
Expand All @@ -373,6 +380,17 @@ export class FUniver {
sheetColumn.setCustomHeader(cfg);
}

customizeRowHeader(cfg: IRowsHeaderCfgParam) {
const wb = this.getActiveWorkbook();
if (!wb) {
console.error('WorkBook not exist');
return;
}
const unitId = wb?.getId();
const sheetRow = this._getSheetRenderComponent(unitId, SHEET_VIEW_KEY.ROW) as SpreadsheetRowHeader;
sheetRow.setCustomHeader(cfg);
}

private _initialize(): void {
this._debouncedFormulaCalculation = debounce(() => {
this._commandService.executeCommand(
Expand Down