mirror of
https://github.com/igorski/bitmappery.git
synced 2026-07-06 15:29:13 +02:00
Restructured layer masking and UI
This commit is contained in:
@@ -31,8 +31,28 @@
|
||||
:class="{
|
||||
'active': layer.index === activeLayerIndex
|
||||
}"
|
||||
@click="setActiveLayerIndex( layer.index )"
|
||||
>{{ layer.name }}</div>
|
||||
>
|
||||
<span
|
||||
class="name"
|
||||
@click="setActiveLayerIndex( layer.index )"
|
||||
:class="{
|
||||
'highlight': layer.index === activeLayerIndex && !activeLayerMask
|
||||
}"
|
||||
>{{ layer.name }}</span>
|
||||
<span
|
||||
v-if="layer.mask"
|
||||
v-t="'mask'"
|
||||
class="mask"
|
||||
:class="{
|
||||
'highlight': layer.mask === activeLayerMask
|
||||
}"
|
||||
@click="setActiveLayerMask( layer.index )"
|
||||
></span>
|
||||
<span
|
||||
class="remove"
|
||||
@click="requestLayerRemove( layer.index )"
|
||||
>×</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
@@ -46,16 +66,9 @@
|
||||
v-t="'addMask'"
|
||||
type="button"
|
||||
class="button button--small"
|
||||
:disabled="currentLayerHasMask"
|
||||
:disabled="!activeLayer || currentLayerHasMask"
|
||||
@click="requestMaskAdd()"
|
||||
></button>
|
||||
<button
|
||||
v-t="'removeLayer'"
|
||||
type="button"
|
||||
class="button button--small"
|
||||
:disabled="!activeLayer"
|
||||
@click="requestLayerRemove()"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -73,6 +86,7 @@ export default {
|
||||
"activeDocument",
|
||||
"activeLayer",
|
||||
"activeLayerIndex",
|
||||
"activeLayerMask",
|
||||
"layers",
|
||||
]),
|
||||
reverseLayers() {
|
||||
@@ -88,6 +102,7 @@ export default {
|
||||
"removeLayer",
|
||||
"updateLayer",
|
||||
"setActiveLayerIndex",
|
||||
"setActiveLayerMask",
|
||||
"addMaskToActiveLayer",
|
||||
]),
|
||||
requestLayerAdd() {
|
||||
@@ -101,8 +116,8 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
requestLayerRemove() {
|
||||
this.removeLayer( this.activeLayer );
|
||||
requestLayerRemove( index ) {
|
||||
this.removeLayer( index );
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -131,16 +146,28 @@ h3 {
|
||||
padding: 0 $spacing-xsmall;
|
||||
@include boxSize();
|
||||
@include customFont();
|
||||
display: flex;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: $color-1;
|
||||
color: #FFF;
|
||||
color: #000;
|
||||
}
|
||||
&.active {
|
||||
padding: $spacing-xsmall $spacing-xsmall;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 3;
|
||||
@include truncate();
|
||||
}
|
||||
.mask {
|
||||
flex: 1;
|
||||
}
|
||||
.highlight {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"layers": "Layers",
|
||||
"addLayer": "Add layer",
|
||||
"addMask": "Add mask",
|
||||
"removeLayer": "Remove layer"
|
||||
"mask": "Mask"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
import { sprite } from "zcanvas";
|
||||
import { createCanvas } from "@/utils/canvas-util";
|
||||
import { LAYER_GRAPHIC, LAYER_MASK } from "@/definitions/layer-types";
|
||||
import { LAYER_GRAPHIC, LAYER_MASK } from "@/definitions/layer-types";
|
||||
|
||||
class LayerSprite extends sprite {
|
||||
constructor( layer ) {
|
||||
@@ -38,7 +38,7 @@ class LayerSprite extends sprite {
|
||||
super({ bitmap, x, y, width, height } );
|
||||
this.setDraggable( true );
|
||||
|
||||
this._layer = layer;
|
||||
this.layer = layer;
|
||||
|
||||
// create brush (always as all layers can be maskable)
|
||||
const brushCanvas = createCanvas();
|
||||
@@ -50,11 +50,11 @@ class LayerSprite extends sprite {
|
||||
}
|
||||
|
||||
isDrawable() {
|
||||
return this._layer.type === LAYER_GRAPHIC || this.isMaskable();
|
||||
return this.layer.type === LAYER_GRAPHIC || this.isMaskable();
|
||||
}
|
||||
|
||||
isMaskable() {
|
||||
return !!this._layer.mask;
|
||||
return !!this.layer.mask;
|
||||
}
|
||||
|
||||
cacheGradient( color, radius = 30 ) {
|
||||
@@ -70,7 +70,7 @@ class LayerSprite extends sprite {
|
||||
|
||||
const gradient = this._brushCtx.createRadialGradient( x, y, innerRadius, x, y, outerRadius );
|
||||
gradient.addColorStop( 0, color );
|
||||
gradient.addColorStop( 1, 'rgba(255,255,255,0)' );
|
||||
gradient.addColorStop( 1, "rgba(255,255,255,0)" );
|
||||
|
||||
this._brushCtx.clearRect( 0, 0, this._brushCvs.width, this._brushCvs.height );
|
||||
this._brushCtx.arc( x, y, radius, 0, 2 * Math.PI );
|
||||
@@ -83,10 +83,11 @@ class LayerSprite extends sprite {
|
||||
// overridden from zCanvas.sprite
|
||||
handleMove( x, y ) {
|
||||
if ( !this.isDrawable() ) {
|
||||
// not drawable, perform default behaviour (drag)
|
||||
return super.handleMove( x, y );
|
||||
}
|
||||
// cache this upfront
|
||||
const ctx = this.isMaskable() ? this._layer.mask.getContext( "2d" ) : this._bitmap.getContext( "2d" );
|
||||
// get the drawing context, cache this upfront
|
||||
const ctx = this.isMaskable() ? this.layer.mask.getContext( "2d" ) : this._bitmap.getContext( "2d" );
|
||||
// note we draw onto the layer bitmap to make this permanent
|
||||
ctx.drawImage( this._brushCvs, x - this._halfRadius, y - this._halfRadius );
|
||||
}
|
||||
|
||||
@@ -31,13 +31,15 @@ export default {
|
||||
documents : [], // opened documents
|
||||
activeIndex: 0, // the currently active document
|
||||
activeLayerIndex: 0, // the currently active layer within the currently active document
|
||||
maskActive: false,
|
||||
},
|
||||
getters: {
|
||||
documents: state => state.documents,
|
||||
activeDocument: state => state.documents[ state.activeIndex ],
|
||||
layers: ( state, getters ) => getters.activeDocument?.layers,
|
||||
activeLayer: ( state, getters ) => getters.layers?.[ state.activeLayerIndex ],
|
||||
activeLayerIndex: state => state.activeLayerIndex,
|
||||
activeLayer: ( state, getters ) => getters.layers?.[ state.activeLayerIndex ],
|
||||
activeLayerMask: ( state, getters ) => ( state.maskActive && getters.activeLayer.mask ) || null,
|
||||
},
|
||||
mutations: {
|
||||
setActiveDocument( state, index ) {
|
||||
@@ -76,9 +78,9 @@ export default {
|
||||
layers.push( LayerFactory.create( opts ) );
|
||||
state.activeLayerIndex = layers.length - 1;
|
||||
},
|
||||
removeLayer( state, layer ) {
|
||||
const index = state.documents[ state.activeIndex ]?.layers.indexOf( layer );
|
||||
if ( index < 0 ) {
|
||||
removeLayer( state, index ) {
|
||||
const layer = state.documents[ state.activeIndex ]?.layers[ index ];
|
||||
if ( !layer ) {
|
||||
return;
|
||||
}
|
||||
flushLayerSprites( layer );
|
||||
@@ -87,8 +89,13 @@ export default {
|
||||
state.activeLayerIndex = Math.max( 0, index - 1 );
|
||||
}
|
||||
},
|
||||
setActiveLayerIndex( state, index ) {
|
||||
state.activeLayerIndex = index;
|
||||
setActiveLayerIndex( state, layerIndex ) {
|
||||
state.activeLayerIndex = layerIndex;
|
||||
state.maskActive = false;
|
||||
},
|
||||
setActiveLayerMask( state, layerIndex ) {
|
||||
state.activeLayerIndex = layerIndex;
|
||||
state.maskActive = !!state.documents[ state.activeIndex ].layers[ layerIndex ].mask;
|
||||
},
|
||||
updateLayer( state, { index, opts = {} }) {
|
||||
const layer = state.documents[ state.activeIndex ].layers[ index ];
|
||||
|
||||
@@ -37,6 +37,11 @@ describe( "Vuex document module", () => {
|
||||
expect( getters.layers( state, mockedGetters )).toEqual( mockedGetters.activeDocument.layers );
|
||||
});
|
||||
|
||||
it( "should be able to retrieve the active Layer index", () => {
|
||||
const state = { activeLayerIndex: 2 };
|
||||
expect( getters.activeLayerIndex( state )).toEqual( 2 );
|
||||
});
|
||||
|
||||
it( "should be able to retrieve the active Layer for the active Document", () => {
|
||||
const state = { activeLayerIndex: 1 };
|
||||
const mockedGetters = {
|
||||
@@ -45,9 +50,16 @@ describe( "Vuex document module", () => {
|
||||
expect( getters.activeLayer( state, mockedGetters )).toEqual( mockedGetters.layers[ 1 ]);
|
||||
});
|
||||
|
||||
it( "should be able to retrieve the active Layer index", () => {
|
||||
const state = { activeLayerIndex: 2 };
|
||||
expect( getters.activeLayerIndex( state )).toEqual( 2 );
|
||||
it( "should be able to retrieve the active Layer mask, when set", () => {
|
||||
const state = { maskActive: false };
|
||||
const mockedGetters = { activeLayer: { name: "layer1" } };
|
||||
// null because mask is not active
|
||||
expect( getters.activeLayerMask( state, mockedGetters )).toBeNull();
|
||||
state.maskActive = true;
|
||||
// null because layer has no mask drawable
|
||||
expect( getters.activeLayerMask( state, mockedGetters )).toBeNull();
|
||||
mockedGetters.activeLayer.mask = { src: "mask" };
|
||||
expect( getters.activeLayerMask( state, mockedGetters )).toEqual( mockedGetters.activeLayer.mask );
|
||||
});
|
||||
});
|
||||
|
||||
@@ -151,7 +163,7 @@ describe( "Vuex document module", () => {
|
||||
};
|
||||
mockUpdateFn = jest.fn();
|
||||
const layer = state.documents[ 0 ].layers[ 1 ];
|
||||
mutations.removeLayer( state, layer );
|
||||
mutations.removeLayer( state, 1 );
|
||||
expect( state.documents[ 0 ].layers ).toEqual([
|
||||
{ name: "layer1" }, { name: "layer3" }
|
||||
]);
|
||||
@@ -160,13 +172,40 @@ describe( "Vuex document module", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to set the active layer index", () => {
|
||||
const state = {
|
||||
documents: [ { name: "foo", layers: [{ name: "layer1" }, { name: "layer2" }] }],
|
||||
activeLayerIndex: 0
|
||||
};
|
||||
mutations.setActiveLayerIndex( state, 1 );
|
||||
expect( state.activeLayerIndex ).toEqual( 1 );
|
||||
describe( "when setting the active layer content", () => {
|
||||
it( "should be able to set the active layer index", () => {
|
||||
const state = {
|
||||
documents: [ { name: "foo", layers: [{ name: "layer1" }, { name: "layer2" }] }],
|
||||
activeLayerIndex: 0
|
||||
};
|
||||
mutations.setActiveLayerIndex( state, 1 );
|
||||
expect( state.activeLayerIndex ).toEqual( 1 );
|
||||
});
|
||||
|
||||
it( "should unset the active layer mask when setting the active layer index", () => {
|
||||
const state = {
|
||||
documents: [ { name: "foo", layers: [{ name: "layer1" }, { name: "layer2" }] }],
|
||||
activeLayerIndex: 0,
|
||||
maskActive: true,
|
||||
};
|
||||
mutations.setActiveLayerIndex( state, 1 );
|
||||
expect( state.maskActive ).toBe( false );
|
||||
});
|
||||
|
||||
it( "should be able to set the active layer mask", () => {
|
||||
const state = {
|
||||
documents: [{
|
||||
name: "foo",
|
||||
layers: [ { name: "layer1" }, { name: "layer2", mask: { src: "mask" } } ]
|
||||
}],
|
||||
activeIndex: 0,
|
||||
activeLayerIndex: 0,
|
||||
maskActive: false,
|
||||
};
|
||||
mutations.setActiveLayerMask( state, 1 );
|
||||
expect( state.activeLayerIndex ).toEqual( 1 );
|
||||
expect( state.maskActive ).toBe( true );
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to update the options of a specific layer within the active Document", () => {
|
||||
|
||||
Reference in New Issue
Block a user