Place assets by dragging them from the panel into the view
The context menu entry is gone; dragging replaces it entirely. The drop target is the view under the pointer, not the active tab, so the same gesture works for scenes and for container templates. While dragging, an outline shows the footprint the object will have plus a crosshair on its origin, turning red when the drop would be refused. Preview and command share one function (placementRejection), so the outline cannot promise something the drop then rejects. A left-press used to select the list row immediately. Since the container tab follows the selection, it switched to the very container being dragged and every drop would have nested it inside itself. The list now selects on release without a drag: picking something up is not the same as choosing it.
This commit is contained in:
1 parent
14562cf157
commit
75d377af39
18 files changed
+545
-77
No files matched your search
@@ -42,6 +42,7 @@ export class Editor implements PlayHost {
|
||||
readonly textureEditor: TextureEditor;
|
||||
readonly containerEditor: ContainerEditor;
|
||||
readonly hierarchy: HierarchyPanel;
|
||||
readonly assets: AssetPanel;
|
||||
tab: CentreTab = 'scene';
|
||||
/** Frame rate, refreshed twice a second; null until the first measurement. */
|
||||
fps: number | null = null;
|
||||
@@ -91,7 +92,8 @@ export class Editor implements PlayHost {
|
||||
|
||||
this.workspace = new HStack();
|
||||
this.leftColumn = new VStack({ gap: 1 });
|
||||
this.leftColumn.add(new AssetPanel(state, () => this.placementTarget(), textures), 1);
|
||||
this.assets = new AssetPanel(state, () => this.placementTarget(), textures);
|
||||
this.leftColumn.add(this.assets, 1);
|
||||
installTextureImport(app.canvas, { state, textures });
|
||||
this.leftColumn.add(this.hierarchy, 1);
|
||||
|
||||
|
||||
@@ -2,7 +2,18 @@ import { Graphics } from 'pixi.js';
|
||||
import type { ContainerAsset } from '../../core/model/Asset';
|
||||
import type { Vec2 } from '../../core/model/Properties';
|
||||
import { findAsset, listContainers } from '../../core/model/Project';
|
||||
import { Button, Dropdown, HStack, Label, Menu, type MenuItem, Spacer, Theme, VStack } from '../../ui';
|
||||
import {
|
||||
activeDrag,
|
||||
Button,
|
||||
Dropdown,
|
||||
HStack,
|
||||
Label,
|
||||
Menu,
|
||||
type MenuItem,
|
||||
Spacer,
|
||||
Theme,
|
||||
VStack,
|
||||
} from '../../ui';
|
||||
import type { EditorState } from '../state';
|
||||
import { ContainerCanvas } from './ContainerCanvas';
|
||||
|
||||
@@ -63,7 +74,12 @@ export class ContainerEditor extends VStack {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/** Middle of the view – where the Assets panel drops what is placed into this template. */
|
||||
/** The pending drop of an asset dragged over the template (test hook). */
|
||||
dropInfo(): ReturnType<ContainerCanvas['dropInfo']> {
|
||||
return this.canvas.dropInfo();
|
||||
}
|
||||
|
||||
/** Middle of the view – where a double-click in the Assets panel places into this template. */
|
||||
worldCenter(): Vec2 {
|
||||
return this.canvas.worldCenter();
|
||||
}
|
||||
@@ -81,8 +97,10 @@ export class ContainerEditor extends VStack {
|
||||
|
||||
private followSelection(): void {
|
||||
const id = this.state.selectedContainerId;
|
||||
// Null merely means "the selection is about something else"; the tab keeps its subject.
|
||||
if (id) this.canvas.show(id);
|
||||
// A dragged row selects on release, and whether that lands before or after the drop is up
|
||||
// to Pixi's event order. Following it in between would swap the tab to the very container
|
||||
// being dragged, which would then nest in itself, so the target stays put while a drag runs.
|
||||
if (id && !activeDrag()) this.canvas.show(id);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { EditorState } from './state';
|
||||
* visible text and to read editor state.
|
||||
*/
|
||||
export function installDebugHook(app: Application, state: EditorState, editor: Editor): void {
|
||||
const { viewport, gameView, graphEditor, textureEditor, containerEditor, hierarchy } = editor;
|
||||
const { viewport, gameView, graphEditor, textureEditor, containerEditor, hierarchy, assets } = editor;
|
||||
const centreOf = (widget: { getGlobalPosition(): { x: number; y: number }; w: number; h: number }) => {
|
||||
const p = widget.getGlobalPosition();
|
||||
return { x: p.x + widget.w / 2, y: p.y + widget.h / 2 };
|
||||
@@ -139,6 +139,10 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
labelsDropped,
|
||||
viewportCenter: () => centreOf(viewport),
|
||||
viewportInfo: () => viewport.debugInfo(),
|
||||
/** Where a dragged asset would land in the Scene tab, and why not when it may not. */
|
||||
dropPreview: () => viewport.dropInfo(),
|
||||
/** An asset row by id – two assets can share a name with the container picker's label. */
|
||||
assetRow: (id: string) => assets.rowGlobal(id),
|
||||
gameInfo: () => gameView.debugInfo(),
|
||||
layerOf: (id: string) => viewport.layerOf(id),
|
||||
drawOrder: () => viewport.drawOrder(),
|
||||
@@ -171,6 +175,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
rect: () => containerEditor.canvasRect(),
|
||||
/** Where a template object is drawn, so a check can click or sample it. */
|
||||
objectScreen: (id: string) => containerEditor.objectScreen(id),
|
||||
dropPreview: () => containerEditor.dropInfo(),
|
||||
},
|
||||
world: () => editor.worldSnapshot(),
|
||||
/** Rows the hierarchy shows – collapsed subtrees are not in here. */
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { CreateAssetCommand, SetActiveSceneCommand } from '../../core/commands/assetCommands';
|
||||
import { nextAssetName } from '../../core/commands/naming';
|
||||
import { PlaceAssetCommand } from '../../core/commands/sceneObjectCommands';
|
||||
import { ASSET_TYPE_LABELS, ASSET_TYPES, type AssetType, hasColorSwatch } from '../../core/model/AssetTypes';
|
||||
import {
|
||||
ASSET_TYPE_LABELS,
|
||||
ASSET_TYPES,
|
||||
type AssetType,
|
||||
hasColorSwatch,
|
||||
isPlaceableType,
|
||||
} from '../../core/model/AssetTypes';
|
||||
import type { Vec2 } from '../../core/model/Properties';
|
||||
import { findAsset, type ObjectHolder } from '../../core/model/Project';
|
||||
import type { TextureStore } from '../../core/store/TextureStore';
|
||||
import { Button, ListView, Menu, type MenuItem, Panel, ScrollContainer } from '../../ui';
|
||||
import { Button, ListView, Menu, type MenuItem, Panel, ScrollContainer, startDrag } from '../../ui';
|
||||
import { confirmDeleteAsset } from '../actions';
|
||||
import { type EditorState, objectSelection } from '../state';
|
||||
import { pickAndImport } from '../textureImport';
|
||||
@@ -47,6 +53,7 @@ export class AssetPanel extends Panel {
|
||||
this.list.onSelect = (id) => state.select(id ? { kind: 'asset', assetId: id } : null);
|
||||
this.list.onActivate = (id) => this.activate(id);
|
||||
this.list.onContextMenu = (id, x, y) => this.openContextMenu(id, x, y);
|
||||
this.list.onDragOut = (id) => this.startPlacementDrag(id);
|
||||
this.scroll.setContent(this.list);
|
||||
this.content.add(this.scroll, 1);
|
||||
|
||||
@@ -100,6 +107,18 @@ export class AssetPanel extends Panel {
|
||||
else this.place(id);
|
||||
}
|
||||
|
||||
/** Where an asset's row sits on screen, so a check can pick it up by id (test hook). */
|
||||
rowGlobal(assetId: string): { x: number; y: number } | null {
|
||||
return this.list.rowGlobal(assetId);
|
||||
}
|
||||
|
||||
/** Hands the asset to the viewport under the pointer; a scene has nowhere to be placed into. */
|
||||
private startPlacementDrag(assetId: string): void {
|
||||
const asset = findAsset(this.state.project, assetId);
|
||||
if (!asset || !isPlaceableType(asset.type)) return;
|
||||
startDrag({ kind: 'asset', id: assetId }, asset.name);
|
||||
}
|
||||
|
||||
private place(assetId: string): void {
|
||||
const target = this.target();
|
||||
if (!target) {
|
||||
@@ -121,13 +140,6 @@ export class AssetPanel extends Panel {
|
||||
onSelect: () => this.state.execute(new SetActiveSceneCommand(id)),
|
||||
disabled: this.state.project.activeSceneId === id,
|
||||
});
|
||||
} else {
|
||||
const target = this.target();
|
||||
items.push({
|
||||
label: target?.holder.type === 'container' ? 'Place in container' : 'Place in scene',
|
||||
onSelect: () => this.place(id),
|
||||
disabled: !target,
|
||||
});
|
||||
}
|
||||
items.push({ label: 'Delete…', danger: true, onSelect: () => confirmDeleteAsset(this.state, id) });
|
||||
const menu = new Menu(items);
|
||||
|
||||
Reference in new issue
Block a user