[FEAT] UI enhancements
This commit is contained in:
@@ -94,10 +94,10 @@
|
||||
:key="`cell-${cellIndex - 1}`"
|
||||
class="absolute"
|
||||
:style="{
|
||||
left: `${getCellPosition(cellIndex - 1).x}px`,
|
||||
top: `${getCellPosition(cellIndex - 1).y}px`,
|
||||
width: `${gridMetrics.maxWidth}px`,
|
||||
height: `${gridMetrics.maxHeight}px`,
|
||||
left: `${Math.round(getCellPosition(cellIndex - 1).x)}px`,
|
||||
top: `${Math.round(getCellPosition(cellIndex - 1).y)}px`,
|
||||
width: `${Math.round(gridMetrics.maxWidth)}px`,
|
||||
height: `${Math.round(gridMetrics.maxHeight)}px`,
|
||||
backgroundColor: getCellBackground(),
|
||||
backgroundImage: getCellBackgroundImage(),
|
||||
backgroundSize: getCellBackgroundSize(),
|
||||
@@ -119,10 +119,10 @@
|
||||
:src="sprite.url"
|
||||
class="absolute pointer-events-none"
|
||||
:style="{
|
||||
left: `${getCellPosition(cellIndex - 1).x + gridMetrics.negativeSpacing + sprite.x}px`,
|
||||
top: `${getCellPosition(cellIndex - 1).y + gridMetrics.negativeSpacing + sprite.y}px`,
|
||||
width: `${sprite.width}px`,
|
||||
height: `${sprite.height}px`,
|
||||
left: `${Math.round(getCellPosition(cellIndex - 1).x + gridMetrics.negativeSpacing + sprite.x)}px`,
|
||||
top: `${Math.round(getCellPosition(cellIndex - 1).y + gridMetrics.negativeSpacing + sprite.y)}px`,
|
||||
width: `${Math.round(sprite.width)}px`,
|
||||
height: `${Math.round(sprite.height)}px`,
|
||||
opacity: '0.25',
|
||||
imageRendering: settingsStore.pixelPerfect ? 'pixelated' : 'auto',
|
||||
}"
|
||||
@@ -143,10 +143,10 @@
|
||||
class="absolute cursor-move transition-transform duration-200"
|
||||
:class="{ 'ring-2 ring-blue-500 ring-offset-1 dark:ring-offset-gray-900': showActiveBorder && selectedSpriteIds.has(sprite.id) }"
|
||||
:style="{
|
||||
left: `${getCellPosition(index).x + gridMetrics.negativeSpacing + sprite.x}px`,
|
||||
top: `${getCellPosition(index).y + gridMetrics.negativeSpacing + sprite.y}px`,
|
||||
width: `${sprite.width}px`,
|
||||
height: `${sprite.height}px`,
|
||||
left: `${Math.round(getCellPosition(index).x + gridMetrics.negativeSpacing + sprite.x)}px`,
|
||||
top: `${Math.round(getCellPosition(index).y + gridMetrics.negativeSpacing + sprite.y)}px`,
|
||||
width: `${Math.round(sprite.width)}px`,
|
||||
height: `${Math.round(sprite.height)}px`,
|
||||
opacity: layer.id === activeLayerId ? '1' : '0.85',
|
||||
imageRendering: settingsStore.pixelPerfect ? 'pixelated' : 'auto',
|
||||
transform: `rotate(${sprite.rotation}deg) scale(${sprite.flipX ? -1 : 1}, ${sprite.flipY ? -1 : 1})`,
|
||||
@@ -165,10 +165,10 @@
|
||||
:src="activeSpriteSprite?.url"
|
||||
class="absolute pointer-events-none"
|
||||
:style="{
|
||||
left: `${ghostSprite.x}px`,
|
||||
top: `${ghostSprite.y}px`,
|
||||
width: `${activeSpriteSprite?.width}px`,
|
||||
height: `${activeSpriteSprite?.height}px`,
|
||||
left: `${Math.round(ghostSprite.x)}px`,
|
||||
top: `${Math.round(ghostSprite.y)}px`,
|
||||
width: `${Math.round(activeSpriteSprite?.width || 0)}px`,
|
||||
height: `${Math.round(activeSpriteSprite?.height || 0)}px`,
|
||||
opacity: '0.6',
|
||||
imageRendering: settingsStore.pixelPerfect ? 'pixelated' : 'auto',
|
||||
transform: `rotate(${activeSpriteSprite?.rotation || 0}deg) scale(${activeSpriteSprite?.flipX ? -1 : 1}, ${activeSpriteSprite?.flipY ? -1 : 1})`,
|
||||
@@ -183,8 +183,8 @@
|
||||
:key="`label-${position.id}`"
|
||||
class="absolute text-[23px] leading-none font-mono text-cyan-600 dark:text-cyan-400 bg-white/90 dark:bg-gray-900/90 px-1 py-0.5 rounded-sm pointer-events-none"
|
||||
:style="{
|
||||
left: `${position.cellX + position.maxWidth - 2}px`,
|
||||
top: `${position.cellY + position.maxHeight - 2}px`,
|
||||
left: `${Math.round(position.cellX + position.maxWidth - 2)}px`,
|
||||
top: `${Math.round(position.cellY + position.maxHeight - 2)}px`,
|
||||
transform: 'translate(-100%, -100%)',
|
||||
}"
|
||||
>
|
||||
@@ -329,8 +329,8 @@
|
||||
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
|
||||
const rows = Math.max(1, Math.ceil(maxLen / props.columns));
|
||||
return {
|
||||
width: gridMetrics.value.maxWidth * props.columns,
|
||||
height: gridMetrics.value.maxHeight * rows,
|
||||
width: Math.round(gridMetrics.value.maxWidth * props.columns),
|
||||
height: Math.round(gridMetrics.value.maxHeight * rows),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -338,8 +338,8 @@
|
||||
const col = index % props.columns;
|
||||
const row = Math.floor(index / props.columns);
|
||||
return {
|
||||
x: col * gridMetrics.value.maxWidth,
|
||||
y: row * gridMetrics.value.maxHeight,
|
||||
x: Math.round(col * gridMetrics.value.maxWidth),
|
||||
y: Math.round(row * gridMetrics.value.maxHeight),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
transformOrigin: 'top left',
|
||||
width: `${cellDimensions.cellWidth}px`,
|
||||
height: `${cellDimensions.cellHeight}px`,
|
||||
backgroundColor: '#f9fafb',
|
||||
backgroundColor: settingsStore.backgroundColor === 'transparent' ? '#f9fafb' : settingsStore.backgroundColor,
|
||||
backgroundImage: getPreviewBackgroundImage(),
|
||||
backgroundSize: settingsStore.backgroundColor === 'transparent' ? '20px 20px' : 'auto',
|
||||
backgroundPosition: settingsStore.backgroundColor === 'transparent' ? '0 0, 0 10px, 10px -10px, -10px 0px' : '0 0',
|
||||
@@ -92,10 +92,10 @@
|
||||
:src="layer.sprites[i - 1].url"
|
||||
class="absolute pointer-events-none"
|
||||
:style="{
|
||||
left: `${cellDimensions.negativeSpacing + layer.sprites[i - 1].x}px`,
|
||||
top: `${cellDimensions.negativeSpacing + layer.sprites[i - 1].y}px`,
|
||||
width: `${layer.sprites[i - 1].width}px`,
|
||||
height: `${layer.sprites[i - 1].height}px`,
|
||||
left: `${Math.round(cellDimensions.negativeSpacing + layer.sprites[i - 1].x)}px`,
|
||||
top: `${Math.round(cellDimensions.negativeSpacing + layer.sprites[i - 1].y)}px`,
|
||||
width: `${Math.round(layer.sprites[i - 1].width)}px`,
|
||||
height: `${Math.round(layer.sprites[i - 1].height)}px`,
|
||||
opacity: '0.3',
|
||||
imageRendering: settingsStore.pixelPerfect ? 'pixelated' : 'auto',
|
||||
transform: `rotate(${layer.sprites[i - 1].rotation || 0}deg) scale(${layer.sprites[i - 1].flipX ? -1 : 1}, ${layer.sprites[i - 1].flipY ? -1 : 1})`,
|
||||
@@ -115,10 +115,10 @@
|
||||
class="absolute"
|
||||
:class="{ 'cursor-move': isDraggable }"
|
||||
:style="{
|
||||
left: `${cellDimensions.negativeSpacing + layer.sprites[currentFrameIndex].x}px`,
|
||||
top: `${cellDimensions.negativeSpacing + layer.sprites[currentFrameIndex].y}px`,
|
||||
width: `${layer.sprites[currentFrameIndex].width}px`,
|
||||
height: `${layer.sprites[currentFrameIndex].height}px`,
|
||||
left: `${Math.round(cellDimensions.negativeSpacing + layer.sprites[currentFrameIndex].x)}px`,
|
||||
top: `${Math.round(cellDimensions.negativeSpacing + layer.sprites[currentFrameIndex].y)}px`,
|
||||
width: `${Math.round(layer.sprites[currentFrameIndex].width)}px`,
|
||||
height: `${Math.round(layer.sprites[currentFrameIndex].height)}px`,
|
||||
imageRendering: settingsStore.pixelPerfect ? 'pixelated' : 'auto',
|
||||
transform: `rotate(${layer.sprites[currentFrameIndex].rotation || 0}deg) scale(${layer.sprites[currentFrameIndex].flipX ? -1 : 1}, ${layer.sprites[currentFrameIndex].flipY ? -1 : 1})`,
|
||||
}"
|
||||
@@ -433,8 +433,8 @@
|
||||
// If manual cell size is enabled, use manual values
|
||||
if (settingsStore.manualCellSizeEnabled) {
|
||||
return {
|
||||
cellWidth: settingsStore.manualCellWidth,
|
||||
cellHeight: settingsStore.manualCellHeight,
|
||||
cellWidth: Math.round(settingsStore.manualCellWidth),
|
||||
cellHeight: Math.round(settingsStore.manualCellHeight),
|
||||
negativeSpacing: 0,
|
||||
};
|
||||
}
|
||||
@@ -442,10 +442,10 @@
|
||||
// Otherwise, calculate from sprite dimensions across ALL layers
|
||||
const { maxWidth, maxHeight } = getMaxDimensionsAcrossLayers(allLayers);
|
||||
const allSprites = allLayers.flatMap(l => l.sprites);
|
||||
const negativeSpacing = calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled);
|
||||
const negativeSpacing = Math.round(calculateNegativeSpacing(allSprites, settingsStore.negativeSpacingEnabled));
|
||||
return {
|
||||
cellWidth: maxWidth + negativeSpacing,
|
||||
cellHeight: maxHeight + negativeSpacing,
|
||||
cellWidth: Math.round(maxWidth + negativeSpacing),
|
||||
cellHeight: Math.round(maxHeight + negativeSpacing),
|
||||
negativeSpacing,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -102,11 +102,11 @@ export function useDragSprite(options: DragSpriteOptions) {
|
||||
lastMaxHeight.value = baseMaxHeight;
|
||||
|
||||
// Calculate negative spacing using shared composable
|
||||
const negativeSpacing = calculateNegativeSpacing(spritesToMeasure, negativeSpacingEnabled);
|
||||
const negativeSpacing = Math.round(calculateNegativeSpacing(spritesToMeasure, negativeSpacingEnabled));
|
||||
|
||||
// Add negative spacing to expand each cell
|
||||
const maxWidth = baseMaxWidth + negativeSpacing;
|
||||
const maxHeight = baseMaxHeight + negativeSpacing;
|
||||
const maxWidth = Math.round(baseMaxWidth + negativeSpacing);
|
||||
const maxHeight = Math.round(baseMaxHeight + negativeSpacing);
|
||||
return { maxWidth, maxHeight, negativeSpacing };
|
||||
};
|
||||
|
||||
@@ -124,14 +124,14 @@ export function useDragSprite(options: DragSpriteOptions) {
|
||||
// (spacing added to top and left)
|
||||
return {
|
||||
id: sprite.id,
|
||||
canvasX: col * maxWidth + negativeSpacing + sprite.x,
|
||||
canvasY: row * maxHeight + negativeSpacing + sprite.y,
|
||||
cellX: col * maxWidth,
|
||||
cellY: row * maxHeight,
|
||||
width: sprite.width,
|
||||
height: sprite.height,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
canvasX: Math.round(col * maxWidth + negativeSpacing + sprite.x),
|
||||
canvasY: Math.round(row * maxHeight + negativeSpacing + sprite.y),
|
||||
cellX: Math.round(col * maxWidth),
|
||||
cellY: Math.round(row * maxHeight),
|
||||
width: Math.round(sprite.width),
|
||||
height: Math.round(sprite.height),
|
||||
maxWidth: Math.round(maxWidth),
|
||||
maxHeight: Math.round(maxHeight),
|
||||
col,
|
||||
row,
|
||||
index,
|
||||
@@ -206,8 +206,8 @@ export function useDragSprite(options: DragSpriteOptions) {
|
||||
// Use the sprite's current index in the array to calculate cell position
|
||||
const cellCol = spriteIndex % columns;
|
||||
const cellRow = Math.floor(spriteIndex / columns);
|
||||
const cellX = cellCol * maxWidth;
|
||||
const cellY = cellRow * maxHeight;
|
||||
const cellX = Math.round(cellCol * maxWidth);
|
||||
const cellY = Math.round(cellRow * maxHeight);
|
||||
|
||||
// Calculate new position relative to cell origin (without the negative spacing offset)
|
||||
// The sprite's x,y is stored relative to where it would be drawn after the negativeSpacing offset
|
||||
@@ -241,8 +241,8 @@ export function useDragSprite(options: DragSpriteOptions) {
|
||||
highlightCell.value = hoverCell;
|
||||
ghostSprite.value = {
|
||||
id: activeSpriteId.value,
|
||||
x: pos.x - dragOffsetX.value,
|
||||
y: pos.y - dragOffsetY.value,
|
||||
x: Math.round(pos.x - dragOffsetX.value),
|
||||
y: Math.round(pos.y - dragOffsetY.value),
|
||||
};
|
||||
onDraw();
|
||||
} else {
|
||||
|
||||
@@ -178,8 +178,33 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Canvas Grid Settings -->
|
||||
<!-- Alignment Tools (Always visible) -->
|
||||
<section>
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">Alignment</h3>
|
||||
<div class="card p-2 bg-gray-50/50 dark:bg-gray-800/40 grid grid-cols-3 gap-2">
|
||||
<Tooltip text="Align sprites to the left edge">
|
||||
<button @click="alignSprites('left')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-left"></i>Left</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Center sprites horizontally">
|
||||
<button @click="alignSprites('center')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrows-left-right"></i>Center</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Align sprites to the right edge">
|
||||
<button @click="alignSprites('right')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-right"></i>Right</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Align sprites to the top edge">
|
||||
<button @click="alignSprites('top')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-up"></i>Top</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Center sprites vertically">
|
||||
<button @click="alignSprites('middle')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrows-up-down"></i>Middle</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Align sprites to the bottom edge">
|
||||
<button @click="alignSprites('bottom')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-down"></i>Bottom</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Canvas Grid Settings (Editor only) -->
|
||||
<section v-if="activeTab === 'canvas'">
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">Grid Layout</h3>
|
||||
<div class="card p-3 bg-gray-50/50 dark:bg-gray-800/40 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -201,8 +226,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- View Options -->
|
||||
<section>
|
||||
<!-- View Options (Editor only) -->
|
||||
<section v-if="activeTab === 'canvas'">
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">View Options</h3>
|
||||
<div class="card p-2 bg-gray-50/50 dark:bg-gray-800/40 grid grid-cols-2 gap-2">
|
||||
<Tooltip text="Disable anti-aliasing for crisp pixel art rendering">
|
||||
@@ -253,8 +278,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Tools -->
|
||||
<section>
|
||||
<!-- Tools (Editor only) -->
|
||||
<section v-if="activeTab === 'canvas'">
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">Tools</h3>
|
||||
<div class="card p-2 bg-gray-50/50 dark:bg-gray-800/40 space-y-2">
|
||||
<div class="flex gap-2">
|
||||
@@ -289,31 +314,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Alignment Tools -->
|
||||
<section>
|
||||
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">Alignment</h3>
|
||||
<div class="card p-2 bg-gray-50/50 dark:bg-gray-800/40 grid grid-cols-3 gap-2">
|
||||
<Tooltip text="Align sprites to the left edge">
|
||||
<button @click="alignSprites('left')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-left"></i>Left</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Center sprites horizontally">
|
||||
<button @click="alignSprites('center')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrows-left-right"></i>Center</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Align sprites to the right edge">
|
||||
<button @click="alignSprites('right')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-right"></i>Right</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Align sprites to the top edge">
|
||||
<button @click="alignSprites('top')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-up"></i>Top</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Center sprites vertically">
|
||||
<button @click="alignSprites('middle')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrows-up-down"></i>Middle</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Align sprites to the bottom edge">
|
||||
<button @click="alignSprites('bottom')" class="w-full flex items-center justify-start gap-2 p-2 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"><i class="fas fa-arrow-down"></i>Bottom</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Footer (Export) -->
|
||||
@@ -370,7 +370,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="activeTab === 'canvas'" class="flex items-center gap-2">
|
||||
<div class="flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg p-1">
|
||||
<button @click="zoomOut" class="p-1.5 hover:bg-white dark:hover:bg-gray-700 rounded text-gray-500 transition-colors"><i class="fas fa-minus text-xs"></i></button>
|
||||
<span class="text-xs font-mono w-12 text-center text-gray-600 dark:text-gray-300">{{ Math.round(zoom * 100) }}%</span>
|
||||
|
||||
Reference in New Issue
Block a user