Added pixel precies grid overlay for pixel art mode

This commit is contained in:
Igor Zinken
2022-01-26 07:55:19 +01:00
parent 6d14abcb60
commit e7232327c4
6 changed files with 93 additions and 13 deletions

View File

@@ -310,10 +310,19 @@
@click="close()"
>
<li>
<button v-t="'snapAlign'" :class="{ checked: snapAlign }" type="button" @click="canSnapAndAlign = !canSnapAndAlign"></button>
<button v-t="'snapAlign'" type="button" :class="{ checked: snapAlign }" @click="canSnapAndAlign = !canSnapAndAlign"></button>
</li>
<li>
<button v-t="'antiAlias'" :class="{ checked: antiAlias }" type="button" @click="useAntiAlias = !useAntiAlias"></button>
<button v-t="'antiAlias'" type="button" :class="{ checked: antiAlias }" @click="useAntiAlias = !useAntiAlias"></button>
</li>
<li>
<button
v-t="'pixelGrid'"
type="button"
:class="{ checked: pixelGrid }"
:disabled="!canUsePixelGrid"
@click="usePixelGrid = !usePixelGrid"
></button>
</li>
</ul>
</li>
@@ -366,6 +375,7 @@
<script>
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
import cloneDeep from "lodash.clonedeep";
import { MAX_SPRITESHEET_WIDTH } from "@/definitions/editor-properties";
import {
CREATE_DOCUMENT, RESIZE_DOCUMENT, EXPORT_DOCUMENT, EXPORT_IMAGE, LOAD_SELECTION, SAVE_SELECTION,
DROPBOX_FILE_SELECTOR, SAVE_DROPBOX_DOCUMENT, PREFERENCES, RESIZE_CANVAS, GRID_TO_LAYERS
@@ -406,6 +416,7 @@ export default {
"getPreferences",
"hasSelection",
"snapAlign",
"pixelGrid",
]),
supportsFullscreen,
noDocumentsAvailable() {
@@ -440,16 +451,32 @@ export default {
await this.storePreferences();
}
},
usePixelGrid: {
get() {
return this.pixelGrid;
},
set( value ) {
this.setPixelGrid( value );
},
},
fullscreenTooltip() {
return `${this.isFullscreen ? this.$t( "minimize" ) : this.$t( "maximize" )} (Shift + F)`;
},
canUsePixelGrid() {
return this.activeDocument?.width <= MAX_SPRITESHEET_WIDTH;
},
},
watch: {
blindActive( isOpen, wasOpen ) {
if ( !isOpen && wasOpen === true ) {
this.setMenuOpened( false );
}
}
},
canUsePixelGrid( value ) {
if ( !value && this.usePixelGrid ) {
this.usePixelGrid = false;
}
},
},
mounted() {
if ( this.$refs.fullscreenBtn ) {
@@ -472,6 +499,7 @@ export default {
"setPreferences",
"setSnapAlign",
"setAntiAlias",
"setPixelGrid",
"updateLayer",
]),
...mapActions([

View File

@@ -40,6 +40,7 @@
"view": "View",
"snapAlign": "Snap / auto align",
"antiAlias": "Anti-alias",
"pixelGrid": "Pixel grid",
"window": "Window",
"noDocumentsOpen": "There are no open documents",
"windowNumName": "Window #{num}: {name}",

View File

@@ -111,6 +111,7 @@ export default {
"antiAlias",
"canvasDimensions",
"snapAlign",
"pixelGrid",
"zoomOptions",
]),
documentTitle() {
@@ -120,6 +121,9 @@ export default {
}
return `${name}.${PROJECT_FILE_EXTENSION}`;
},
hasGuideRenderer() {
return this.snapAlign || this.pixelGrid;
},
},
watch: {
windowSize() {
@@ -194,7 +198,7 @@ export default {
zCanvas.addChild( sprite );
});
zCanvas?.interactionPane.stayOnTop();
this.snapAlign && guideRenderer.stayOnTop();
this.hasGuideRenderer && guideRenderer.stayOnTop();
},
},
activeLayer( layer ) {
@@ -242,9 +246,15 @@ export default {
selectMode( value ) {
this.updateInteractionPane();
},
snapAlign( value ) {
hasGuideRenderer( value ) {
getCanvasInstance()?.[ value ? "addChild" : "removeChild" ]( guideRenderer );
},
snapAlign( value ) {
this.updateGuideModes();
},
pixelGrid( value ) {
this.updateGuideModes();
},
antiAlias( value ) {
getCanvasInstance()?.setSmoothing( value );
},
@@ -287,7 +297,8 @@ export default {
handler: this.handleCanvasEvent.bind( this ),
}, this.$store, this.calcIdealDimensions );
setCanvasInstance( zCanvas );
guideRenderer = new GuideRenderer( this.snapAlign ? zCanvas : null );
guideRenderer = new GuideRenderer( this.hasGuideRenderer ? zCanvas : null );
this.updateGuideModes();
return zCanvas;
},
cacheContainerSize() {
@@ -389,6 +400,9 @@ export default {
break;
}
},
updateGuideModes() {
guideRenderer.setModes( this.snapAlign, this.pixelGrid );
},
handleGuides() {
if ( !this.snapAlign ) {
return;

View File

@@ -1,7 +1,7 @@
/**
* The MIT License (MIT)
*
* Igor Zinken 2021 - https://www.igorski.nl
* Igor Zinken 2021-2022 - https://www.igorski.nl
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -26,6 +26,8 @@ import { fastRound } from "@/math/unit-math";
import { isCoordinateInHorizontalRange, isCoordinateInVerticalRange } from "@/math/point-math";
import { getClosestSnappingPoints } from "@/rendering/snapping";
const AMOUNT_OF_PIXELS = 1; // currently only 1 pixel grid supported
class GuideRenderer extends sprite {
constructor( zCanvasInstance = null ) {
super({ interactive: false });
@@ -40,12 +42,42 @@ class GuideRenderer extends sprite {
zCanvas?.addChild( this );
}
setModes( drawGuides, drawPixelGrid ) {
this.drawGuides = drawGuides;
this.drawPixelGrid = drawPixelGrid;
}
draw( ctx, viewport = null ) {
if ( !this.canvas.guides || !this.canvas.draggingSprite ) {
/* grid */
if ( this.drawPixelGrid ) {
const width = viewport?.width || this.canvas.getWidth();
const height = viewport?.height || this.canvas.getHeight();
ctx.strokeStyle = "000";
ctx.lineWidth = 1 / this.canvas.zoomFactor;
ctx.beginPath();
for ( let x = 0; x < width; x += AMOUNT_OF_PIXELS ) {
ctx.moveTo( x, 0 );
ctx.lineTo( x, height );
}
for ( let y = 0; y < height; y += AMOUNT_OF_PIXELS ) {
ctx.moveTo( 0, y );
ctx.lineTo( width, y );
}
ctx.stroke();
}
/* guides */
if ( !this.drawGuides || !this.canvas.guides || !this.canvas.draggingSprite ) {
return;
}
const left = viewport?.left || 0;
const top = viewport?.top || 0;
const left = viewport?.left || 0;
const top = viewport?.top || 0;
// we can snap the currently draggingSprite against its edge and center
const guides = getClosestSnappingPoints( this.canvas.draggingSprite, this.canvas.guides );

View File

@@ -650,8 +650,8 @@ class LayerSprite extends ZoomableSprite {
const destX = x - viewport.left;
const destY = y - viewport.top;
if ( this.isRotated()) {
const tX = destX + ( width * .5 );
const tY = destY + ( height * .5 );
const tX = destX + ( width * HALF );
const tY = destY + ( height * HALF );
documentContext.translate( tX, tY );
documentContext.rotate( this.layer.effects.rotation );
documentContext.translate( -tX, -tY );
@@ -684,7 +684,7 @@ function rotatePointerLists( pointers, layer, sourceWidth, sourceHeight ) {
pointers.forEach( point => {
// translate recorded pointer towards rotated point
// and against layer position
const p = translatePointerRotation( point.x - x, point.y - y, sourceWidth * 0.5, sourceHeight * 0.5, rotation );
const p = translatePointerRotation( point.x - x, point.y - y, sourceWidth * HALF, sourceHeight * HALF, rotation );
if ( mirrorX ) {
p.x -= sourceWidth;
}

View File

@@ -43,6 +43,7 @@ export default {
},
snapAlign : true,
antiAlias : true,
pixelGrid : false,
},
getters: {
activeTool : state => state.activeTool,
@@ -55,6 +56,7 @@ export default {
cloneOptions : state => state.options[ ToolTypes.CLONE ],
snapAlign : state => state.snapAlign,
antiAlias : state => state.antiAlias,
pixelGrid : state => state.pixelGrid,
},
mutations: {
setActiveTool( state, { tool, document }) {
@@ -86,6 +88,9 @@ export default {
setAntiAlias( state, value ) {
state.antiAlias = value;
},
setPixelGrid( state, value ) {
state.pixelGrid = value;
},
},
};