feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)
* feat(platform-common): 添加WASM加载器和环境检测API * feat(rapier2d): 新增Rapier2D WASM绑定包 * feat(physics-rapier2d): 添加跨平台WASM加载器 * feat(asset-system): 添加运行时资产目录和bundle格式 * feat(asset-system-editor): 新增编辑器资产管理包 * feat(editor-core): 添加构建系统和模块管理 * feat(editor-app): 重构浏览器预览使用import maps * feat(platform-web): 添加BrowserRuntime和资产读取 * feat(engine): 添加材质系统和着色器管理 * feat(material): 新增材质系统和着色器编辑器 * feat(tilemap): 增强tilemap编辑器和动画系统 * feat(modules): 添加module.json配置 * feat(core): 添加module.json和类型定义更新 * chore: 更新依赖和构建配置 * refactor(plugins): 更新插件模板使用ModuleManifest * chore: 添加第三方依赖库 * chore: 移除BehaviourTree-ai和ecs-astar子模块 * docs: 更新README和文档主题样式 * fix: 修复Rust文档测试和添加rapier2d WASM绑定 * fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题 * feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea) * fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖 * fix: 添加缺失的包依赖修复CI构建 * fix: 修复CodeQL检测到的代码问题 * fix: 修复构建错误和缺失依赖 * fix: 修复类型检查错误 * fix(material-system): 修复tsconfig配置支持TypeScript项目引用 * fix(editor-core): 修复Rollup构建配置添加tauri external * fix: 修复CodeQL检测到的代码问题 * fix: 修复CodeQL检测到的代码问题
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import { useTilemapEditorStore, type TileSelection } from '../stores/TilemapEditorStore';
|
||||
import type { ITilesetData, ITileAnimation } from '@esengine/tilemap';
|
||||
|
||||
interface TilesetPreviewProps {
|
||||
imageUrl: string;
|
||||
@@ -11,7 +12,10 @@ interface TilesetPreviewProps {
|
||||
tileHeight: number;
|
||||
columns: number;
|
||||
rows: number;
|
||||
tileset?: ITilesetData;
|
||||
animatedTileIds?: Set<number>;
|
||||
onSelectionChange?: (selection: TileSelection) => void;
|
||||
onEditAnimation?: (tileId: number) => void;
|
||||
}
|
||||
|
||||
export const TilesetPreview: React.FC<TilesetPreviewProps> = ({
|
||||
@@ -20,7 +24,10 @@ export const TilesetPreview: React.FC<TilesetPreviewProps> = ({
|
||||
tileHeight,
|
||||
columns,
|
||||
rows,
|
||||
tileset,
|
||||
animatedTileIds,
|
||||
onSelectionChange,
|
||||
onEditAnimation,
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -29,6 +36,7 @@ export const TilesetPreview: React.FC<TilesetPreviewProps> = ({
|
||||
const [selectionStart, setSelectionStart] = useState<{ x: number; y: number } | null>(null);
|
||||
const [selectionEnd, setSelectionEnd] = useState<{ x: number; y: number } | null>(null);
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; tileId: number } | null>(null);
|
||||
|
||||
const selectedTiles = useTilemapEditorStore(state => state.selectedTiles);
|
||||
const setSelectedTiles = useTilemapEditorStore(state => state.setSelectedTiles);
|
||||
@@ -101,7 +109,24 @@ export const TilesetPreview: React.FC<TilesetPreviewProps> = ({
|
||||
selectedTiles.height * tileHeight - 2
|
||||
);
|
||||
}
|
||||
}, [image, columns, rows, tileWidth, tileHeight, selectedTiles, isSelecting, selectionStart, selectionEnd]);
|
||||
|
||||
// Draw animation indicators
|
||||
if (animatedTileIds && animatedTileIds.size > 0) {
|
||||
for (const tileId of animatedTileIds) {
|
||||
const x = (tileId % columns) * tileWidth;
|
||||
const y = Math.floor(tileId / columns) * tileHeight;
|
||||
|
||||
// Draw small play icon in bottom-right corner
|
||||
ctx.fillStyle = 'rgba(0, 180, 0, 0.9)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + tileWidth - 12, y + tileHeight - 10);
|
||||
ctx.lineTo(x + tileWidth - 12, y + tileHeight - 2);
|
||||
ctx.lineTo(x + tileWidth - 4, y + tileHeight - 6);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}, [image, columns, rows, tileWidth, tileHeight, selectedTiles, isSelecting, selectionStart, selectionEnd, animatedTileIds]);
|
||||
|
||||
useEffect(() => {
|
||||
draw();
|
||||
@@ -184,12 +209,47 @@ export const TilesetPreview: React.FC<TilesetPreviewProps> = ({
|
||||
setSelectionEnd(null);
|
||||
};
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
if (!onEditAnimation) return;
|
||||
|
||||
const coords = getTileCoords(e);
|
||||
const tileId = coords.y * columns + coords.x;
|
||||
|
||||
setContextMenu({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
tileId
|
||||
});
|
||||
};
|
||||
|
||||
const _handleCloseContextMenu = () => {
|
||||
setContextMenu(null);
|
||||
};
|
||||
|
||||
const handleEditAnimation = () => {
|
||||
if (contextMenu && onEditAnimation) {
|
||||
onEditAnimation(contextMenu.tileId);
|
||||
}
|
||||
setContextMenu(null);
|
||||
};
|
||||
|
||||
// Close context menu when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClick = () => setContextMenu(null);
|
||||
if (contextMenu) {
|
||||
document.addEventListener('click', handleClick);
|
||||
return () => document.removeEventListener('click', handleClick);
|
||||
}
|
||||
}, [contextMenu]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
@@ -204,7 +264,43 @@ export const TilesetPreview: React.FC<TilesetPreviewProps> = ({
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
onContextMenu={handleContextMenu}
|
||||
/>
|
||||
{contextMenu && (
|
||||
<div
|
||||
className="tileset-context-menu"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: contextMenu.x,
|
||||
top: contextMenu.y,
|
||||
background: '#252526',
|
||||
border: '1px solid #3c3c3c',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 0',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
style={{
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
padding: '6px 16px',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: '#e0e0e0',
|
||||
fontSize: '12px',
|
||||
textAlign: 'left',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.background = '#094771'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.background = 'none'}
|
||||
onClick={handleEditAnimation}
|
||||
>
|
||||
编辑动画... (瓦片 #{contextMenu.tileId})
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user