refactor(editor): separate the element renders (#11461)
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-block-surface": "workspace:*",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-gfx-text": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { FontFamily } from '@blocksuite/affine-model';
|
||||
|
||||
export const GROUP_TITLE_FONT = FontFamily.Inter;
|
||||
export const GROUP_TITLE_FONT_SIZE = 12;
|
||||
export const GROUP_TITLE_PADDING = [2, 0];
|
||||
export const GROUP_TITLE_OFFSET = 4;
|
||||
67
blocksuite/affine/gfx/group/src/element-renderer/index.ts
Normal file
67
blocksuite/affine/gfx/group/src/element-renderer/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
type CanvasRenderer,
|
||||
type ElementRenderer,
|
||||
ElementRendererExtension,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { GroupElementModel } from '@blocksuite/affine-model';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
|
||||
import { titleRenderParams } from './utils.js';
|
||||
|
||||
export const group: ElementRenderer<GroupElementModel> = (
|
||||
model,
|
||||
ctx,
|
||||
matrix,
|
||||
renderer
|
||||
) => {
|
||||
const { xywh } = model;
|
||||
const bound = Bound.deserialize(xywh);
|
||||
const elements = renderer.provider.selectedElements?.() || [];
|
||||
|
||||
const renderParams = titleRenderParams(model, renderer.viewport.zoom);
|
||||
model.externalXYWH = renderParams.titleBound.serialize();
|
||||
|
||||
ctx.setTransform(matrix);
|
||||
|
||||
if (elements.includes(model.id)) {
|
||||
if (model.showTitle) {
|
||||
renderTitle(model, ctx, renderer, renderParams);
|
||||
} else {
|
||||
ctx.lineWidth = 2 / renderer.viewport.zoom;
|
||||
ctx.strokeStyle = renderer.getPropertyValue('--affine-blue');
|
||||
ctx.strokeRect(0, 0, bound.w, bound.h);
|
||||
}
|
||||
} else if (model.childElements.some(child => elements.includes(child.id))) {
|
||||
ctx.lineWidth = 2 / renderer.viewport.zoom;
|
||||
ctx.strokeStyle = '#8FD1FF';
|
||||
ctx.strokeRect(0, 0, bound.w, bound.h);
|
||||
|
||||
if (model.showTitle) renderTitle(model, ctx, renderer, renderParams);
|
||||
}
|
||||
};
|
||||
|
||||
export const GroupElementRendererExtension = ElementRendererExtension(
|
||||
'group',
|
||||
group
|
||||
);
|
||||
|
||||
function renderTitle(
|
||||
model: GroupElementModel,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
renderer: CanvasRenderer,
|
||||
renderParams: ReturnType<typeof titleRenderParams>
|
||||
) {
|
||||
const { text, lineHeight, font, padding, offset, titleBound } = renderParams;
|
||||
|
||||
model.externalXYWH = titleBound.serialize();
|
||||
|
||||
ctx.translate(0, -offset);
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.font = font;
|
||||
ctx.fillStyle = renderer.getPropertyValue('--affine-blue');
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(text, padding[0], -lineHeight / 2 - padding[1]);
|
||||
}
|
||||
77
blocksuite/affine/gfx/group/src/element-renderer/utils.ts
Normal file
77
blocksuite/affine/gfx/group/src/element-renderer/utils.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
getFontString,
|
||||
getLineHeight,
|
||||
getLineWidth,
|
||||
truncateTextByWidth,
|
||||
} from '@blocksuite/affine-gfx-text';
|
||||
import type { GroupElementModel } from '@blocksuite/affine-model';
|
||||
import { FontWeight } from '@blocksuite/affine-model';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
|
||||
import {
|
||||
GROUP_TITLE_FONT,
|
||||
GROUP_TITLE_FONT_SIZE,
|
||||
GROUP_TITLE_OFFSET,
|
||||
GROUP_TITLE_PADDING,
|
||||
} from './consts.js';
|
||||
|
||||
export function titleRenderParams(group: GroupElementModel, zoom: number) {
|
||||
let text = group.title.toString().trim();
|
||||
const font = getGroupTitleFont(zoom);
|
||||
const lineWidth = getLineWidth(text, font);
|
||||
const lineHeight = getLineHeight(
|
||||
GROUP_TITLE_FONT,
|
||||
GROUP_TITLE_FONT_SIZE / zoom,
|
||||
'normal'
|
||||
);
|
||||
const bound = group.elementBound;
|
||||
const padding = [
|
||||
GROUP_TITLE_PADDING[0] / zoom,
|
||||
GROUP_TITLE_PADDING[1] / zoom,
|
||||
];
|
||||
const offset = GROUP_TITLE_OFFSET / zoom;
|
||||
|
||||
let titleWidth = lineWidth + padding[0] * 2;
|
||||
const titleHeight = lineHeight + padding[1] * 2;
|
||||
|
||||
if (titleWidth > bound.w) {
|
||||
text = truncateTextByWidth(text, font, bound.w - 10);
|
||||
text = text.slice(0, text.length - 1) + '..';
|
||||
titleWidth = bound.w;
|
||||
}
|
||||
|
||||
return {
|
||||
font,
|
||||
bound,
|
||||
text,
|
||||
titleWidth,
|
||||
titleHeight,
|
||||
offset,
|
||||
lineHeight,
|
||||
padding,
|
||||
titleBound: new Bound(
|
||||
bound.x,
|
||||
bound.y - titleHeight - offset,
|
||||
titleWidth,
|
||||
titleHeight
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function titleBound(group: GroupElementModel, zoom: number) {
|
||||
const { titleWidth, titleHeight, bound } = titleRenderParams(group, zoom);
|
||||
|
||||
return new Bound(bound.x, bound.y - titleHeight, titleWidth, titleHeight);
|
||||
}
|
||||
|
||||
function getGroupTitleFont(zoom: number) {
|
||||
const fontSize = GROUP_TITLE_FONT_SIZE / zoom;
|
||||
const font = getFontString({
|
||||
fontSize,
|
||||
fontFamily: GROUP_TITLE_FONT,
|
||||
fontWeight: FontWeight.Regular,
|
||||
fontStyle: 'normal',
|
||||
});
|
||||
|
||||
return font;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './command';
|
||||
export * from './element-renderer';
|
||||
export * from './text/text';
|
||||
export * from './toolbar/config';
|
||||
export * from './view';
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import {
|
||||
GROUP_TITLE_FONT_SIZE,
|
||||
GROUP_TITLE_OFFSET,
|
||||
GROUP_TITLE_PADDING,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { GroupElementModel } from '@blocksuite/affine-model';
|
||||
import type { RichText } from '@blocksuite/affine-rich-text';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
@@ -21,6 +16,12 @@ import { html, nothing } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import {
|
||||
GROUP_TITLE_FONT_SIZE,
|
||||
GROUP_TITLE_OFFSET,
|
||||
GROUP_TITLE_PADDING,
|
||||
} from '../element-renderer/consts';
|
||||
|
||||
export function mountGroupTitleEditor(
|
||||
group: GroupElementModel,
|
||||
edgeless: BlockComponent
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"references": [
|
||||
{ "path": "../../blocks/block-surface" },
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../text" },
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
|
||||
Reference in New Issue
Block a user