子画布展开改用真实坐标与视口承载
资源卡拖拽使用模型坐标换算,消除阻力和回弹 子画布空白区域支持画布平移,保持进入退出动画连贯
This commit is contained in:
@@ -5184,6 +5184,14 @@ iframe.preview-frame {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.game-resource-book-scene--child {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.game-resource-book-scene--child.is-measuring {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.game-resource-book-scene.is-measuring {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -5197,6 +5205,7 @@ iframe.preview-frame {
|
||||
#fffdfa;
|
||||
content: '';
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 180ms ease;
|
||||
}
|
||||
|
||||
@@ -5338,6 +5347,12 @@ iframe.preview-frame {
|
||||
box-shadow 180ms ease;
|
||||
}
|
||||
|
||||
.game-resource-book-scene--idle
|
||||
.game-resource-book-scene-card
|
||||
.game-resource-card {
|
||||
transition: box-shadow 180ms ease;
|
||||
}
|
||||
|
||||
.game-resource-book-scene--main
|
||||
.game-resource-book-scene-card
|
||||
.game-resource-card {
|
||||
|
||||
@@ -326,15 +326,6 @@ function defaultResourceCanvasViewports(): ResourceCanvasViewportBySortMode {
|
||||
|
||||
const RESOURCE_CANVAS_FALLBACK_WIDTH = 900;
|
||||
const RESOURCE_CANVAS_FALLBACK_HEIGHT = 640;
|
||||
const RESOURCE_CANVAS_MIN_DRAG_COORDINATE = -1_000_000;
|
||||
const RESOURCE_CANVAS_MAX_DRAG_COORDINATE = 1_000_000;
|
||||
|
||||
function normalizeResourceCanvasDragCoordinate(value: number) {
|
||||
return Math.min(
|
||||
RESOURCE_CANVAS_MAX_DRAG_COORDINATE,
|
||||
Math.max(RESOURCE_CANVAS_MIN_DRAG_COORDINATE, Math.round(value)),
|
||||
);
|
||||
}
|
||||
|
||||
function resourceCanvasViewportElement(element: HTMLElement | null) {
|
||||
const pageCanvas = element?.querySelector<HTMLElement>(
|
||||
@@ -979,9 +970,13 @@ function ResourceBookScene({
|
||||
positions,
|
||||
cardSizes,
|
||||
visibleResourceIds,
|
||||
sceneSize,
|
||||
viewport,
|
||||
renderCard,
|
||||
onCollapse,
|
||||
onPointerDown,
|
||||
onPointerMove,
|
||||
onPointerUp,
|
||||
onPointerCancel,
|
||||
}: {
|
||||
resources: ProjectResource[];
|
||||
visibleCategoryOrder: readonly ResourceCategory[];
|
||||
@@ -992,7 +987,7 @@ function ResourceBookScene({
|
||||
positions: Map<string, { x: number; y: number; section?: ResourceCategory }>;
|
||||
cardSizes: Map<string, ResourceCanvasCardSize>;
|
||||
visibleResourceIds: Set<string>;
|
||||
sceneSize: { width: number; height: number };
|
||||
viewport: CanvasViewport;
|
||||
renderCard: (
|
||||
resource: ProjectResource,
|
||||
layout: {
|
||||
@@ -1001,9 +996,15 @@ function ResourceBookScene({
|
||||
width: number;
|
||||
height: number;
|
||||
rotation: number;
|
||||
dragX: number;
|
||||
dragY: number;
|
||||
},
|
||||
) => ReactNode;
|
||||
onCollapse: () => void;
|
||||
onPointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
||||
onPointerMove: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
||||
onPointerUp: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
||||
onPointerCancel: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
||||
}) {
|
||||
const resourcesByCategory = new Map<ResourceCategory, ProjectResource[]>();
|
||||
resources.forEach((resource) => {
|
||||
@@ -1019,6 +1020,10 @@ function ResourceBookScene({
|
||||
return (
|
||||
<div
|
||||
className={`game-resource-book-scene game-resource-book-scene--${resourceBookView} game-resource-book-scene--${resourceBookTransition}${overviewReady ? '' : ' is-measuring'}`}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerCancel}
|
||||
>
|
||||
{visibleCategoryOrder.map((category) => {
|
||||
const rect = overviewRects.get(category);
|
||||
@@ -1083,60 +1088,18 @@ function ResourceBookScene({
|
||||
const overviewTop =
|
||||
(rect?.top ?? 0) + 58 + Math.min(stackIndex, 2) * 5;
|
||||
const overflow = stackIndex >= 3;
|
||||
const positionedItems = categoryResources.map((item) => ({
|
||||
resource: item,
|
||||
position: positions.get(item.id),
|
||||
size: cardSizes.get(item.id) ?? {
|
||||
width: 180,
|
||||
height: 128,
|
||||
},
|
||||
}));
|
||||
const positioned = positionedItems.filter(
|
||||
(item) => item.position,
|
||||
) as Array<{
|
||||
resource: ProjectResource;
|
||||
position: { x: number; y: number };
|
||||
size: ResourceCanvasCardSize;
|
||||
}>;
|
||||
const minX = positioned.length
|
||||
? Math.min(...positioned.map((item) => item.position.x))
|
||||
: 0;
|
||||
const minY = positioned.length
|
||||
? Math.min(...positioned.map((item) => item.position.y))
|
||||
: 0;
|
||||
const maxX = positioned.length
|
||||
? Math.max(
|
||||
...positioned.map(
|
||||
(item) => item.position.x + item.size.width,
|
||||
),
|
||||
)
|
||||
: 720;
|
||||
const maxY = positioned.length
|
||||
? Math.max(
|
||||
...positioned.map(
|
||||
(item) => item.position.y + item.size.height,
|
||||
),
|
||||
)
|
||||
: 480;
|
||||
const availableWidth = Math.max(320, sceneSize.width - 96);
|
||||
const availableHeight = Math.max(240, sceneSize.height - 96);
|
||||
const expandedScale = Math.min(
|
||||
1,
|
||||
availableWidth / Math.max(1, maxX - minX),
|
||||
availableHeight / Math.max(1, maxY - minY),
|
||||
);
|
||||
const expandedLeft = position
|
||||
? 48 + (position.x - minX) * expandedScale
|
||||
: 48 + (stackIndex % 4) * 180;
|
||||
const expandedTop = position
|
||||
? 48 + (position.y - minY) * expandedScale
|
||||
: 48 + Math.floor(stackIndex / 4) * 150;
|
||||
const expanded = active;
|
||||
const layout = {
|
||||
x: expanded ? expandedLeft : overviewLeft,
|
||||
y: expanded ? expandedTop : overviewTop,
|
||||
width: expanded ? size.width * expandedScale : 92,
|
||||
height: expanded ? size.height * expandedScale : 64,
|
||||
x: expanded
|
||||
? (position?.x ?? 0) * viewport.scale + viewport.x
|
||||
: overviewLeft,
|
||||
y: expanded
|
||||
? (position?.y ?? 0) * viewport.scale + viewport.y
|
||||
: overviewTop,
|
||||
width: expanded ? size.width * viewport.scale : 92,
|
||||
height: expanded ? size.height * viewport.scale : 64,
|
||||
dragX: expanded ? (position?.x ?? 0) : 0,
|
||||
dragY: expanded ? (position?.y ?? 0) : 0,
|
||||
rotation: expanded
|
||||
? 0
|
||||
: (stackIndex % 2 ? -1 : 1) * (stackIndex + 0.5),
|
||||
@@ -2443,6 +2406,15 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
|
||||
if (resourceBookView === 'child') {
|
||||
const switchFitBounds = resourceCanvasContentBounds(
|
||||
resourcePositionsByCategory.get(category) ?? [],
|
||||
resourceCardSizeByResourceId,
|
||||
);
|
||||
const switchFitViewport = fitResourceCanvasViewportToContent({
|
||||
bounds: switchFitBounds,
|
||||
canvasSize: resourceBookSceneSize,
|
||||
padding: RESOURCE_CANVAS_FIT_PADDING,
|
||||
});
|
||||
setResourceBookTransition('returning');
|
||||
scheduleResourceBookTransition(() => {
|
||||
setActiveResourceCategory(category);
|
||||
@@ -2450,6 +2422,7 @@ export default function ProjectDevelopmentView({
|
||||
setResourceBookView('main');
|
||||
setResourceBookTransition('idle');
|
||||
window.requestAnimationFrame(() => {
|
||||
setResourceCanvasViewport(category, switchFitViewport);
|
||||
setResourceBookView('child');
|
||||
setResourceBookTransition('entering');
|
||||
scheduleResourceBookTransition(() => {
|
||||
@@ -2460,6 +2433,18 @@ export default function ProjectDevelopmentView({
|
||||
return;
|
||||
}
|
||||
|
||||
const fitBounds = resourceCanvasContentBounds(
|
||||
resourcePositionsByCategory.get(category) ?? [],
|
||||
resourceCardSizeByResourceId,
|
||||
);
|
||||
setResourceCanvasViewport(
|
||||
category,
|
||||
fitResourceCanvasViewportToContent({
|
||||
bounds: fitBounds,
|
||||
canvasSize: resourceBookSceneSize,
|
||||
padding: RESOURCE_CANVAS_FIT_PADDING,
|
||||
}),
|
||||
);
|
||||
setActiveResourceCategory(category);
|
||||
setResourceBookCategory(category);
|
||||
setResourceBookView('child');
|
||||
@@ -2474,8 +2459,12 @@ export default function ProjectDevelopmentView({
|
||||
cancelResourceCardDrag,
|
||||
resourceBookCategory,
|
||||
resourceBookManagerRef,
|
||||
resourceBookSceneSize,
|
||||
resourceBookTransition,
|
||||
resourceBookView,
|
||||
resourceCardSizeByResourceId,
|
||||
resourcePositionsByCategory,
|
||||
setResourceCanvasViewport,
|
||||
scheduleResourceBookTransition,
|
||||
],
|
||||
);
|
||||
@@ -3497,7 +3486,7 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
drag.changed = true;
|
||||
const coordinate = (start: number, delta: number) =>
|
||||
normalizeResourceCanvasDragCoordinate(start + delta / drag.startScale);
|
||||
start + delta / drag.startScale;
|
||||
const preview = {
|
||||
resourceId: drag.resourceId,
|
||||
x: coordinate(drag.startX, deltaX),
|
||||
@@ -3528,14 +3517,12 @@ export default function ProjectDevelopmentView({
|
||||
const preview = drag.changed
|
||||
? {
|
||||
resourceId: drag.resourceId,
|
||||
x: normalizeResourceCanvasDragCoordinate(
|
||||
x:
|
||||
drag.startX +
|
||||
(event.clientX - drag.startClientX) / drag.startScale,
|
||||
),
|
||||
y: normalizeResourceCanvasDragCoordinate(
|
||||
(event.clientX - drag.startClientX) / drag.startScale,
|
||||
y:
|
||||
drag.startY +
|
||||
(event.clientY - drag.startClientY) / drag.startScale,
|
||||
),
|
||||
(event.clientY - drag.startClientY) / drag.startScale,
|
||||
}
|
||||
: null;
|
||||
setResourceCardDragPreview(null);
|
||||
@@ -3577,7 +3564,7 @@ export default function ProjectDevelopmentView({
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.closest(
|
||||
'button, input, textarea, select, a, .game-resource-card',
|
||||
'button, input, textarea, select, a, .game-resource-card, .game-resource-book-scene-titlebar',
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@@ -4780,6 +4767,8 @@ export default function ProjectDevelopmentView({
|
||||
width: number;
|
||||
height: number;
|
||||
rotation: number;
|
||||
dragX: number;
|
||||
dragY: number;
|
||||
},
|
||||
) => {
|
||||
const previewIdentity = resourceCardPreviews.identityByResourceId.get(
|
||||
@@ -4792,6 +4781,13 @@ export default function ProjectDevelopmentView({
|
||||
resourceCardDragPreview?.resourceId === resource.id
|
||||
? resourceCardDragPreview
|
||||
: null;
|
||||
const viewport = activeResourceCanvasViewport;
|
||||
const previewX = dragPreview
|
||||
? dragPreview.x * viewport.scale + viewport.x
|
||||
: undefined;
|
||||
const previewY = dragPreview
|
||||
? dragPreview.y * viewport.scale + viewport.y
|
||||
: undefined;
|
||||
return (
|
||||
<ResourceCard
|
||||
resource={resource}
|
||||
@@ -4801,8 +4797,8 @@ export default function ProjectDevelopmentView({
|
||||
? 'version-binding'
|
||||
: null
|
||||
}
|
||||
x={dragPreview?.x ?? layout.x}
|
||||
y={dragPreview?.y ?? layout.y}
|
||||
x={previewX ?? layout.x}
|
||||
y={previewY ?? layout.y}
|
||||
rotation={layout.rotation}
|
||||
dragging={dragPreview !== null}
|
||||
cardSize={{ width: layout.width, height: layout.height }}
|
||||
@@ -4815,7 +4811,12 @@ export default function ProjectDevelopmentView({
|
||||
onSelect={handleResourceSelect}
|
||||
onOpenEditor={handleResourceCardOpenEditor}
|
||||
onPointerDown={(event) =>
|
||||
handleResourceCardPointerDown(event, resource, layout.x, layout.y)
|
||||
handleResourceCardPointerDown(
|
||||
event,
|
||||
resource,
|
||||
layout.dragX,
|
||||
layout.dragY,
|
||||
)
|
||||
}
|
||||
onPointerMove={handleResourceCardPointerMove}
|
||||
onPointerUp={handleResourceCardPointerUp}
|
||||
@@ -4831,6 +4832,7 @@ export default function ProjectDevelopmentView({
|
||||
},
|
||||
[
|
||||
activeCardMediaIdentity,
|
||||
activeResourceCanvasViewport,
|
||||
handleCardMediaPlaybackChange,
|
||||
handleCardMediaReady,
|
||||
handleCardMediaToggle,
|
||||
@@ -5379,9 +5381,13 @@ export default function ProjectDevelopmentView({
|
||||
positions={resourcePositionById}
|
||||
cardSizes={resourceCardSizeByResourceId}
|
||||
visibleResourceIds={visibleResourceIds}
|
||||
sceneSize={resourceBookSceneSize}
|
||||
viewport={activeResourceCanvasViewport}
|
||||
renderCard={renderResourceBookCard}
|
||||
onCollapse={returnToResourceBookMain}
|
||||
onPointerDown={handleResourceCanvasPointerDown}
|
||||
onPointerMove={handleResourceCanvasPointerMove}
|
||||
onPointerUp={stopResourceCanvasPan}
|
||||
onPointerCancel={stopResourceCanvasPan}
|
||||
/>
|
||||
<label className="game-resource-search">
|
||||
<Search size={16} aria-hidden="true" />
|
||||
|
||||
Reference in New Issue
Block a user