Add textures for sprites
Images are uploaded to a content-addressed blob endpoint and stored as data/textures/<sha256>.<ext>; the project only keeps the file name. The sprite points at a texture asset through an ordinary property of the new kind `asset`, so instance overrides, "Apply to container", "Reset to asset", undo and `Set property` work without any extra code. The new kind also replaces the ad-hoc asset picker the graph nodes faked as an enum. Import goes through + -> Texture, drag & drop onto the canvas or Ctrl+V. Images wider than 2048px are downscaled client-side and re-encoded to WebP if needed, because nginx caps uploads at 1MB; an image that already fits is uploaded byte for byte so pixel art is never resampled. The sprite colour default changes from red to white, since colour tints the texture multiplicatively and a tint of white shows an image as it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6168df41f8
commit
5a6af74e44
47 files changed
+1995
-110
No files matched your search
@@ -4,9 +4,11 @@ import { PlaceAssetCommand } from '../../core/commands/sceneObjectCommands';
|
||||
import { ASSET_TYPE_LABELS, ASSET_TYPES, type AssetType, hasColorSwatch } from '../../core/model/AssetTypes';
|
||||
import type { Vec2 } from '../../core/model/Properties';
|
||||
import { findAsset } from '../../core/model/Project';
|
||||
import type { TextureStore } from '../../core/store/TextureStore';
|
||||
import { Button, ListView, Menu, type MenuItem, Panel, ScrollContainer } from '../../ui';
|
||||
import { confirmDeleteAsset } from '../actions';
|
||||
import { type EditorState, objectSelection } from '../state';
|
||||
import { pickAndImport } from '../textureImport';
|
||||
|
||||
/** Lists all assets of the project; creates, places and deletes them. */
|
||||
export class AssetPanel extends Panel {
|
||||
@@ -16,6 +18,8 @@ export class AssetPanel extends Panel {
|
||||
constructor(
|
||||
private readonly state: EditorState,
|
||||
private readonly placementPoint: () => Vec2,
|
||||
/** Where imported images go. Without it the Texture entry has nothing to upload to. */
|
||||
private readonly textures: TextureStore | null = null,
|
||||
) {
|
||||
super({ title: 'Assets', padding: 0, gap: 0 });
|
||||
this.minWidth = 160;
|
||||
@@ -25,7 +29,7 @@ export class AssetPanel extends Panel {
|
||||
new Menu(
|
||||
ASSET_TYPES.map((type) => ({
|
||||
label: ASSET_TYPE_LABELS[type],
|
||||
onSelect: () => this.createAsset(type),
|
||||
onSelect: () => (type === 'texture' ? this.importTextures() : this.createAsset(type)),
|
||||
})),
|
||||
).openBelow(add);
|
||||
this.header.add(add);
|
||||
@@ -67,6 +71,18 @@ export class AssetPanel extends Panel {
|
||||
if (this.state.execute(command)) this.state.select({ kind: 'asset', assetId: command.asset.id });
|
||||
}
|
||||
|
||||
/**
|
||||
* A texture is an uploaded picture, so its menu entry opens the file dialog instead of
|
||||
* creating an empty asset – a texture without an image would draw nothing.
|
||||
*/
|
||||
private importTextures(): void {
|
||||
if (!this.textures) {
|
||||
this.state.setNotice('Image upload is not available.');
|
||||
return;
|
||||
}
|
||||
pickAndImport({ state: this.state, textures: this.textures });
|
||||
}
|
||||
|
||||
/** Double-click / Enter: scenes become active, everything else is placed. */
|
||||
private activate(id: string): void {
|
||||
const asset = findAsset(this.state.project, id);
|
||||
|
||||
Reference in new issue
Block a user