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: support grid layout #240

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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 @@ -191,7 +191,7 @@ export class CharacterChart<T extends IChartGraphicAttribute>
}

protected getViewBoxFromSpec() {
const layout = getLayoutFromWidget(this._config.position, this);
const layout = getLayoutFromWidget(this._config, this);
const viewBox = {
x1: layout.x,
x2: layout.x + layout.width,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class CommonLayoutRuntime implements IChartCharacterRuntime {
applyConfigToAttribute(character: ICharacterChart): void {
const rawAttribute = character.getRuntimeConfig().getAttribute();
const config = character.getRuntimeConfig().config;
const layoutData = getLayoutFromWidget(config.position, character);
const layoutData = getLayoutFromWidget(config, character);
const viewBox = {
x1: 0,
x2: layoutData.width,
Expand Down
4 changes: 2 additions & 2 deletions packages/vstory-core/src/character/component/runtime/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export class BaseGraphicRuntime implements IComponentCharacterRuntime {

applyConfigToAttribute(character: ICharacterComponent): void {
const rawAttribute = character.getAttribute();
const { options, position, locked } = character.config;
const layout = getLayoutFromWidget(position, character);
const { options, locked } = character.config;
const layout = getLayoutFromWidget(character.config, character);

const { graphic = {}, text = {}, panel = {}, padding } = options;

Expand Down
4 changes: 2 additions & 2 deletions packages/vstory-core/src/character/component/runtime/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export class TextRuntime implements IComponentCharacterRuntime {

applyConfigToAttribute(character: ICharacterComponent): void {
const rawAttribute = character.getAttribute();
const { options, position } = character.config;
const layout = getLayoutFromWidget(position, character);
const { options } = character.config;
const layout = getLayoutFromWidget(character.config, character);

const { graphic = {}, panel = {}, padding } = options;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class CharacterTable<T extends ITableGraphicAttribute>
}

protected getViewBoxFromSpec() {
const layout = getLayoutFromWidget(this._config.position, this);
const layout = getLayoutFromWidget(this._config, this);
const viewBox = {
x1: layout.x,
x2: layout.x + layout.width,
Expand Down
25 changes: 24 additions & 1 deletion packages/vstory-core/src/core/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface IStoryInitOption {
scaleX?: number | 'auto';
scaleY?: number | 'auto';
theme?: string;
dslOptions?: Omit<IStoryDSL, 'characters' | 'acts'>;
}

export class Story extends EventEmitter implements IStory {
Expand All @@ -36,6 +37,7 @@ export class Story extends EventEmitter implements IStory {
protected _player: IPlayer;
protected _characterTree: ICharacterTree;
protected _theme: string;
protected _dslOptions: Omit<IStoryDSL, 'characters' | 'acts'>;
pluginService: IPluginService;

get canvas(): IStoryCanvas {
Expand All @@ -50,6 +52,10 @@ export class Story extends EventEmitter implements IStory {
return this._theme;
}

get dslOptions(): Omit<IStoryDSL, 'characters' | 'acts'> {
return this._dslOptions;
}

constructor(dsl: IStoryDSL | null, option: IStoryInitOption) {
super();
this.id = `test-mvp_${Generator.GenAutoIncrementId()}`;
Expand All @@ -64,7 +70,8 @@ export class Story extends EventEmitter implements IStory {
layerViewBox,
dpr = vglobal.devicePixelRatio,
scaleX = 1,
scaleY = 1
scaleY = 1,
dslOptions = { version: '0.0.2', width: option.width, height: option.height }
} = option;
if (!(dom || canvas)) {
throw new Error('dom or canvas is required');
Expand All @@ -83,11 +90,26 @@ export class Story extends EventEmitter implements IStory {
});
this._characterTree = new CharacterTree(this);
this._dsl = dsl;
if (dsl) {
const options = { ...dsl };
delete options.characters;
delete options.acts;
this._dslOptions = options;
} else {
this._dslOptions = dslOptions;
}
this._theme = theme;
this.pluginService = new DefaultPluginService();
this.pluginService.active(this, {
pluginList: []
});

// TODO 兼容历史版本,后续版本删除
if (!(this._dslOptions.width && this._dslOptions.height)) {
console.warn('width and height is required in dslOptions');
}
this._dslOptions.width = this._dslOptions.width ?? this._canvas.getStage().width;
this._dslOptions.height = this._dslOptions.height ?? this._canvas.getStage().height;
}

init(player: IPlayer) {
Expand All @@ -112,6 +134,7 @@ export class Story extends EventEmitter implements IStory {
}
toDSL(): IStoryDSL {
return {
...this._dslOptions,
acts: this._player.toDSL(),
characters: this._characterTree.toDSL()
};
Expand Down
59 changes: 45 additions & 14 deletions packages/vstory-core/src/interface/dsl/dsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,20 @@ export interface IActionPayload {
export type IActionSpec = IAction<IActionPayload>;

export type IWidgetData = {
// 网格布局定位
columnSpan?: [number, number];
rowSpan?: [number, number];

left?: number;
top?: number;
x?: number;
y?: number;
bottom?: number;
right?: number;
width?: number;
height?: number;

angle?: number;
anchor?: [number, number];
} & (
| {
bottom?: number;
right?: number;
}
| {
width?: number;
height?: number;
}
);
};

export interface IActSpec {
id: string;
Expand All @@ -69,13 +67,34 @@ export type ISceneSpec = {
export interface ICharacterConfigBase {
id: string;
type: string; // 类型
layoutType?: 'absolute' | 'grid' | 'flex'; // 布局类型
// flex布局配置
flexConfig?: {
direction: 'row' | 'column';
wrap: 'wrap' | 'nowrap';
justifyContent: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
alignItems: 'flex-start' | 'flex-end' | 'center';
};
// 网格布局配置
gridConfig?: {
columns: number;
rows: number;
gutterColumn: number;
gutterRow: number;
};
position: IWidgetData; // 定位描述
zIndex: number;
theme?: string;
extra?: any; // 带着的额外信息
locked?: boolean; // 是否锁定
}

// 新增 container 类型的配置接口
export interface IContainerCharacterConfig extends ICharacterConfigBase {
type: 'container';
children: ICharacterConfig[]; // 子元素
}

export type IEditorTextGraphicAttribute = {
graphicAlign?: 'left' | 'center' | 'right';
graphicBaseline?: 'top' | 'middle' | 'bottom';
Expand All @@ -85,7 +104,8 @@ export type ICharacterConfig =
| IChartCharacterConfig
| IComponentCharacterConfig
| ITableCharacterConfig
| IPivotChartCharacterConfig;
| IPivotChartCharacterConfig
| IContainerCharacterConfig; // 添加 container 类型

export type IUpdateConfigParams = Omit<Partial<ICharacterConfig>, 'id' | 'type'>;

Expand All @@ -101,6 +121,17 @@ export interface ICharacterConstructor {
}

export interface IStoryDSL {
acts: IActSpec[]; // 作品的章节
version: string; // 版本号
width: number;
height: number;
theme?: string; // 主题
background?: string; // 背景色
gridConfig?: {
columns: number;
rows: number;
gutterColumn: number;
gutterRow: number;
};
characters: ICharacterConfig[]; // 作品中的元素
acts: IActSpec[]; // 作品的章节
}
3 changes: 3 additions & 0 deletions packages/vstory-core/src/interface/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface IStory extends IReleaseable, EventEmitter {
readonly player: IPlayer;
readonly theme: string;

// 忽略IStoryDSL中的characters和actions
readonly dslOptions: Omit<IStoryDSL, 'characters' | 'acts'>;

load: (dsl: IStoryDSL) => void;
reset: () => void;
toDSL: () => IStoryDSL;
Expand Down
53 changes: 37 additions & 16 deletions packages/vstory-core/src/utils/layout.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { IRect } from '@visactor/vrender-core';
import type { IWidgetData } from '../interface/dsl/dsl';
import type { ICharacterConfigBase } from '../interface/dsl/dsl';
import type { ICharacter, ILayoutLine } from '../interface/character';
import type { IAABBBounds } from '@visactor/vutils';

Expand All @@ -15,27 +14,49 @@ export interface ILayoutAttribute {
// shapePoints?: IPointLike[];
}

export function getLayoutFromWidget(w: Partial<IWidgetData> | IRect, character: ICharacter): Partial<ILayoutAttribute> {
const x = 'x' in w ? w.x : w.left;
const y = 'y' in w ? w.y : w.top;
let width = (w as any).width;
let height = (w as any).height;
const stage = character.canvas.getStage();
if (!isFinite(width) && isFinite((w as any).right)) {
width = stage.width - x - (w as any).right;
}
if (!isFinite(height) && isFinite((w as any).bottom)) {
height = stage.height - y - (w as any).bottom;
export function getLayoutFromWidget(config: ICharacterConfigBase, character: ICharacter): Partial<ILayoutAttribute> {
const { position = {} } = config;
let x: number;
let y: number;
let width: number;
let height: number;
const { gridConfig, width: boxWidth, height: boxHeight } = character.story.dslOptions;

const { columnSpan, rowSpan } = position;
if (position.columnSpan && position.rowSpan && position.columnSpan.length === 2 && position.rowSpan.length === 2) {
const { columns, rows, gutterColumn, gutterRow } = gridConfig;
const columnWidth = (boxWidth - (columns - 1) * gutterColumn) / columns;
const rowHeight = (boxHeight - (rows - 1) * gutterRow) / rows;
x = columnSpan[0] * columnWidth + columnSpan[0] * gutterColumn;
y = rowSpan[0] * rowHeight + rowSpan[0] * gutterRow;
const deltaColumn = columnSpan[1] - columnSpan[0];
const deltaRow = rowSpan[1] - rowSpan[0];
width = deltaColumn * columnWidth + Math.max(0, deltaColumn - 1) * gutterColumn;
height = deltaRow * rowHeight + Math.max(0, deltaRow - 1) * gutterRow;
} else {
// 兼容旧版的xy
if ((position as any).x || (position as any).y) {
console.warn('布局将不再支持x、y属性,请尽快改为使用left、top属性');
}
x = position.left ?? (position as any).x;
y = position.top ?? (position as any).y;
width = position.width;
height = position.height;

if (!isFinite(width) && isFinite(position.right)) {
width = boxWidth - x - position.right;
}
if (!isFinite(height) && isFinite(position.bottom)) {
height = boxHeight - y - position.bottom;
}
}
// const width = 'width' in w ? w.width : <number>(w as any).right - <number>w.left;
// const height = 'height' in w ? w.height : <number>(w as any).bottom - <number>w.top;

return {
x,
y,
width: isFinite(width) ? width : void 0,
height: isFinite(height) ? height : void 0,
angle: (w as any).angle ?? 0,
angle: position.angle ?? 0,
anchor: [x + width / 2, y + height / 2].map(item => (isFinite(item) ? item : 0)) as [number, number]
};
}
Expand Down
10 changes: 10 additions & 0 deletions packages/vstory/demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { TableTheme } from './demos/table/runtime/theme';
import { TableStyle } from './demos/table/runtime/style';
import { TableVisible } from './demos/table/runtime/visible';
import { SpecMarker } from './demos/chart/runtime/spec-marker';
import { LayoutGridComponent } from './demos/layout/grid';

type MenusType = (
| {
Expand Down Expand Up @@ -259,6 +260,15 @@ const App = () => {
}
]
},
{
name: 'Layout',
subMenus: [
{
name: 'Grid',
component: LayoutGridComponent
}
]
},
{
name: 'Infographic',
subMenus: [
Expand Down
Loading
Loading