import { ConfigEntity, EntityOpts } from '../../../common'; import { Disposable, Emitter } from '@gedit/utils'; import type { PlaygroundConfigEntity } from './playground-config-entity'; import { PathPointSelectionEntity } from './path-point-selection-entity'; import { isOSX } from '@gedit/application-common'; /** * 编辑态 */ export interface EditorState { id: string; icon?: string; title?: string | ((config: PlaygroundConfigEntity) => string); showTitle?: boolean; keybinding?: string; disabled?: boolean | ((config: PlaygroundConfigEntity) => boolean); hidden?: boolean | ((config: PlaygroundConfigEntity) => boolean); // 隐藏 cursor?: string; // 光标类型 shortcut?: string; // 快捷键 ctr?: boolean; // 是否需要按住ctrl, ios 下为 command shortcutAutoEsc?: boolean; // 点击快捷键后自动退出到默认 order?: number; // 组里排序 priority?: number; // 排序 handle?: (config: PlaygroundConfigEntity, e?: EditorStateChangeEvent) => void; // 触发逻辑 disableSelector?: boolean; // 切换后会把选择器隐藏了 state?: EditorState[]; cancelMode: | 'esc' // 按住esc则退 | 'once' // 触发一次后则退出 | 'hold' // 点击会保持该状态 | 'text'; // 文字形式 cancelFactor?: ( config: PlaygroundConfigEntity, e?: EditorStateChangeEvent ) => boolean; // 退出条件 onEsc?: (config: PlaygroundConfigEntity, e?: KeyboardEvent) => void; } export namespace EditorState { export const STATE_SELECT: EditorState = { id: 'STATE_SELECT', icon: 'gedit-toolbar-select', title: '选择元素', cursor: '', shortcut: 'V', priority: 1, order: 0, cancelMode: 'hold', }; export const STATE_GRAB: EditorState = { id: 'STATE_GRAB', icon: 'gedit-toolbar-grab', cursor: 'grab', title: '拖动画布', shortcut: 'SPACE', shortcutAutoEsc: true, priority: 1, order: 1, cancelMode: 'hold', }; // 功能模块,只监听 alt 键; export const SHOW_SCENE_RANGE_RULER: EditorState = { id: 'SHOW_SCENE_RANGE_RULER', shortcut: 'ALT', cancelMode: 'hold', title: '显示元素到场景边缘的距离', shortcutAutoEsc: true, hidden: true, priority: 1, order: 2, state: [EditorState.STATE_SELECT], }; export const STATE_ZOOM_CENTER: EditorState = { id: 'STATE_ZOOM_CENTER', icon: 'gedit-toolbar-origin', title: '重置', shortcut: '0', ctr: true, hidden: config => !config.zoomEnable, handle: config => { config.scrollPageBoundsToCenter(); }, priority: 2, order: 4, cancelMode: 'once', }; export const STATE_ZOOM_IN: EditorState = { id: 'STATE_ZOOM_IN', icon: 'gedit-toolbar-bigger', title: '放大', shortcut: '=', ctr: true, hidden: config => !config.zoomEnable, handle: config => { config.zoomin(); }, priority: 2, order: 1, cursor: 'zoom-in', cancelMode: 'once', }; export const STATE_ZOOM_OUT: EditorState = { id: 'STATE_ZOOM_OUT', icon: 'gedit-toolbar-smaller', title: '缩小', shortcut: '-', ctr: true, cursor: 'zoom-out', hidden: config => !config.zoomEnable, handle: config => { config.zoomout(); }, priority: 2, order: 2, cancelMode: 'once', }; export const STATE_ZOOM_TEXT: EditorState = { id: 'STATE_ZOOM_TEXT', title: config => `${Math.round(config.config.zoom * 100)}%`, showTitle: true, priority: 2, order: 3, cancelMode: 'text', }; export const EDIT_PATH_STATE: EditorState = { id: 'ADD_PATH', icon: 'gedit-toolbar-pen', title: '失量', shortcut: 'P', // cursor: 'url(https://gw.alipayobjects.com/zos/bmw-prod/8dc2fcc4-279b-408c-886c-60fdba4c1f1e.svg), copy', // shortcutAutoEsc: true, disableSelector: true, priority: 0, order: 1, cancelMode: 'esc', cancelFactor: a => { const pathPointSelection = a.entityManager.getEntity( PathPointSelectionEntity ); const bool = !pathPointSelection?.selection?.length; // pathPointSelection.selectMode === PathSelectMode.SELECT; if (!bool) { // 选中路径点时,按esc则取消选中 pathPointSelection.exitPathMode(); } return bool; }, handle: (a, b) => { const currentSelectedNodes = a.context.selection.selectedNodes; if ( currentSelectedNodes.length !== 1 || currentSelectedNodes[0].displayType !== 'path' ) { // 路径选中时先取消所有 selection; a.context.selection.clearSelection(); } }, }; } export const EDITOR_STATE_DEFAULTS: EditorState[] = [ EditorState.STATE_SELECT, EditorState.STATE_GRAB, EditorState.STATE_ZOOM_IN, EditorState.STATE_ZOOM_OUT, EditorState.STATE_ZOOM_CENTER, EditorState.STATE_ZOOM_TEXT, EditorState.SHOW_SCENE_RANGE_RULER, ]; export interface EditorStateChangeEvent { state: EditorState; event?: React.MouseEvent; lastState?: EditorState; } /** * 编辑状态管理 */ export class EditorStateConfigEntity extends ConfigEntity { static type = 'EditorStateConfigEntity'; playgroundId?: string; protected states = EDITOR_STATE_DEFAULTS.slice(); protected stateGroups: EditorState[][] = []; protected selected: string = EditorState.STATE_SELECT.id; protected onStateChangeEmitter = new Emitter(); readonly onStateChange = this.onStateChangeEmitter.event; constructor(opts: EntityOpts) { super(opts); this.toDispose.push(this.onStateChangeEmitter); this.sortStates(); } /** * 取消指定状态后触发 * @param stateId * @param fn */ onCancel(stateId: string, fn: () => void): Disposable { return this.onStateChange(e => { if (e.lastState && e.lastState.id === stateId) { fn(); } }); } getCurrentState(): EditorState | undefined { return this.states.find(s => s.id === this.selected); } is(stateId: string): boolean { return this.selected === stateId; } changeState(stateId: string, event?: React.MouseEvent): void { const state = this.states.find(s => s.id === stateId); if (!state) throw new Error('Unknown editor state ' + stateId); if (this.selected !== stateId) { const lastState = this.getCurrentState(); this.selected = stateId; this.onStateChangeEmitter.fire({ state, event, lastState }); this.fireChanged(); } } toDefaultState(): void { this.changeState(EditorState.STATE_SELECT.id); } registerState(state: EditorState): void { if (this.states.find(s => s.id === state.id)) { return; } this.states.push(state); this.sortStates(); this.fireChanged(); } protected sortStates(): void { const states: { [key: number]: EditorState[] } = {}; this.states.forEach(c => { const priority = c.priority === undefined ? 1 : c.priority; states[priority] = states[priority] || []; states[priority].push(c); states[priority] = states[priority].sort( (a, b) => (a.order || 0) - (b.order || 0) ); }); this.stateGroups = Object.keys(states) .sort((a, b) => parseFloat(a || '0') - parseFloat(b || '0')) .map(k => states[parseFloat(k)]); } removeState(stateId: string): void { this.states = this.states.filter(s => s.id !== stateId); this.fireChanged(); } getStates(): EditorState[] { return this.states; } /** * state 分组显示 */ getStateGroups(): EditorState[][] { return this.stateGroups; } getStateFormShortCut(e: KeyboardEvent): EditorState | undefined { return this.states.find(s => { const shortcut = s.shortcut === 'SPACE' ? ' ' : (s.shortcut || '').toLowerCase(); const isCtr = isOSX ? e.metaKey : e.ctrlKey; const isKey = e.key.toLowerCase() === shortcut; const isCurrentState = s.state ? s.state.some(c => c.id === this.selected) : true; if (isCurrentState && (s.ctr ? isCtr && isKey : isKey)) { return s; } }); } }