LAYERS (wip) - brushing preview broken

This commit is contained in:
Hugh Bord 2021-08-14 16:41:42 +10:00
parent a168d1cf8d
commit 4c38f83503
17 changed files with 797 additions and 165 deletions

View File

@ -64,17 +64,17 @@ ASCIIBIRD is mostly usable. There are some bugs however to note at the moment.
* Redo (ctrl y) is a buggy
* Circle brush (works okay for odd width and height numbers)
* Importer could be re-written with regex
* Exporter will default transparent bg to black by default, which wont for some asciis
## Focusing on Now
* Modals to add
* Asciibird options / Options modal from skgs PR
* Review encodings check on file import - UTF8 vs Latin something
* Context Menus (right click menu) - add to other areas of asciibird
* LAYERS, drag and drop to arrange layers
* Image overlay for trace mode
* Experimental code to only render blocks visible on screen
* Review encodings check on file import - UTF8 vs Latin something
# Keyboard Shortcuts
## ASCII Editing
@ -92,7 +92,7 @@ Until the keyboard shortcuts are moved out of `Editor.vue` they will only work w
* When the colour picker is open, the first 0 to 9 colours can be chosen with the keyboards number.
* When the character picker is open, you can also press any key on your keyboard to set the character.
## Toolbar Keyboard Shortcusts
## Toolbar Keyboard Shortcuts
The toolbar keyboard shorts are used with the ALT key.
* Alt 1 to 8 - Will toggle the corresponding toolbar icon
@ -118,7 +118,7 @@ The toolbar keyboard shorts are used with the ALT key.
* Ctrl + c - Copy blocks to clipboard
* Ctrl + x - Cut blocks to clipboard
* Ctrl + v - Paste blocks
* Ctrl + v - Paste blocks as brush
* Delete - Delete selected blocks
### Brush Mode Only

View File

@ -25,6 +25,7 @@
"vue-draggable-resizable": "^2.3.0",
"vue-tailwind": "^2.0.0",
"vue-toasted": "^1.1.28",
"vuedraggable": "^2.24.3",
"vuex": "^3.4.0",
"vuex-persist": "^3.1.3"
},

View File

@ -92,6 +92,8 @@
/>
<BrushLibrary v-if="brushLibraryState.visible" />
<BrushPreview />
</template>
<template v-else>
<div
@ -131,8 +133,11 @@ import EditAscii from "./components/modals/EditAscii.vue";
import PasteAscii from "./components/modals/PasteAscii.vue";
import BrushCanvas from "./components/parts/BrushCanvas.vue";
import BrushPreview from "./components/parts/BrushPreview.vue";
// import KeyboardShortcuts from "./components/parts/KeyboardShortcuts.vue";
import draggable from "vuedraggable";
import {
parseMircAscii,
toolbarIcons,
@ -159,6 +164,8 @@ export default {
PasteAscii,
BrushLibrary,
BrushCanvas,
BrushPreview,
draggable,
// KeyboardShortcuts
},
name: "Dashboard",

File diff suppressed because one or more lines are too long

View File

@ -3,8 +3,8 @@
<vue-draggable-resizable
@dragstop="onDragStop"
@resizestop="onResize"
:grid="[currentAscii.blockWidth, currentAscii.blockHeight]"
:min-width="blockWidth * 25"
:grid="[blockWidth, blockHeight]"
:min-width="blockWidth * 35"
:max-width="blockWidth * 50"
:min-height="blockHeight * 50"
:max-height="blockHeight * 100"
@ -36,10 +36,23 @@
>History</t-button
>
<t-button
type="button"
:class="`block w-full ${
panel.tab === 1
? 'border-gray-900 bg-blue-500'
: 'border-gray-200 bg-gray-500'
}`"
@click="changeTab(2)"
>Layers</t-button
>
<div class="flex">
<div v-if="panel.tab === 0">
<div v-for="(brush, key) in brushHistory" :key="key">
<t-card class="hover:border-blue-900 border-gray-300 bg-gray-200 mt-2">
<t-card
class="hover:border-blue-900 border-gray-300 bg-gray-200 mt-2"
>
<BrushCanvas :blocks="decompressBlock(brush.blocks)" />
<t-button
@ -97,17 +110,38 @@
</t-card>
</div>
</div>
<div
v-if="panel.tab === 2"
class="w-full bg-white rounded-lg shadow-lg"
>
<Layers />
</div>
</div>
</t-card>
</vue-draggable-resizable>
</div>
</template>
<style scoped>
.buttons {
margin-top: 35px;
}
.ghost {
opacity: 0.5;
background: #c8ebfb;
}
</style>
<script>
import { mircColours99, blockWidth, blockHeight } from "../ascii";
import BrushCanvas from "./parts/BrushCanvas.vue";
import BrushPreview from "./parts/BrushPreview.vue";
import Layers from "./parts/Layers.vue";
import LZString from "lz-string";
export default {
name: "BrushLibrary",
created() {
@ -125,17 +159,23 @@ export default {
y: 100,
visible: true,
tab: 1,
dragging: false,
},
}),
components: {
BrushCanvas,
BrushPreview,
Layers
},
computed: {
blockWidth() {
return blockWidth;
return blockWidth * this.blockSizeMultiplier;
},
blockHeight() {
return blockHeight;
return blockHeight * this.blockSizeMultiplier;
},
blockSizeMultiplier() {
return this.$store.getters.blockSizeMultiplier;
},
currentAscii() {
return this.$store.getters.currentAscii;

View File

@ -2,7 +2,7 @@
<div>
<vue-draggable-resizable
@dragstop="onDragStop"
:grid="[currentAscii.blockWidth, currentAscii.blockHeight]"
:grid="[blockWidth, blockHeight]"
:min-width="blockWidth * 40"
:max-width="blockWidth * 40"
:min-height="blockHeight * 20"
@ -80,10 +80,13 @@ export default {
}),
computed: {
blockWidth() {
return blockWidth;
return blockWidth * this.blockSizeMultiplier;
},
blockHeight() {
return blockHeight;
return blockHeight * this.blockSizeMultiplier;
},
blockSizeMultiplier() {
return this.$store.getters.blockSizeMultiplier
},
getToolName() {
return toolbarIcons[this.$store.getters.currentTool] ? toolbarIcons[this.$store.getters.currentTool].name : 'none';
@ -153,10 +156,10 @@ export default {
return this.$store.getters.brushBlocks;
},
// canvasX() {
// return this.x * this.currentAscii.blockWidth;
// return this.x * this.blockWidth;
// },
// canvasY() {
// return this.y * this.currentAscii.blockHeight;
// return this.y * this.blockHeight;
// },
toolbarState() {
return this.$store.getters.toolbarState;

View File

@ -2,12 +2,12 @@
<div>
<vue-draggable-resizable
@dragstop="onDragStop"
:grid="[currentAscii.blockWidth, currentAscii.blockHeight]"
:grid="[blockWidth, blockHeight]"
style="z-index: 5"
:min-width="blockWidth * 25"
:max-width="blockWidth * 30"
:max-height="blockHeight * 39"
:min-height="blockHeight * 38"
:max-width="blockWidth * 40"
:max-height="blockHeight * 20"
:min-height="blockHeight * 19"
:w="toolbarState.w"
:h="toolbarState.h"
:x="toolbarState.x"
@ -50,7 +50,7 @@
<div class="flex">
<label class="ml-1 w-1/2">
<t-checkbox
name="targetingFg"
name="mirror-x"
v-model="mirror.x"
@change="updateMirror()"
/>
@ -58,7 +58,7 @@
</label>
<label class="ml-1 w-1/2">
<t-checkbox
name="targetingBg"
name="mirror-y"
v-model="mirror.y"
@change="updateMirror()"
/>
@ -66,6 +66,25 @@
</label>
</div>
<div class="flex">
<label class="ml-1 w-1/2">
<t-checkbox
name="update-brush"
v-model="toolbarState.updateBrush"
@change="$store.commit('toggleUpdateBrush', updateBrush)"
/>
<span class="text-sm">Update Brush</span>
</label>
<label class="ml-1 w-1/2">
<t-checkbox
name="grid"
v-model="toolbarState.gridView"
@change="$store.commit('toggleGridView', gridView)"
/>
<span class="text-sm">Grid</span>
</label>
</div>
<hr class="m-3" />
<t-button
@ -82,9 +101,6 @@
<font-awesome-icon :icon="[value.fa, value.icon]" size="lg" />
</t-button>
<hr class="m-3" />
<small>Brush</small>
<BrushPreview />
</t-card>
</vue-draggable-resizable>
</div>
@ -92,7 +108,6 @@
<script>
import Colours from "./Colours.vue";
import BrushPreview from "./parts/BrushPreview.vue";
import { toolbarIcons, blockWidth, blockHeight } from "../ascii";
export default {
@ -106,7 +121,7 @@ export default {
this.mirror.y = this.toolbarState.mirrorY;
},
name: "Toolbar",
components: { Colours, BrushPreview },
components: { Colours },
data: () => ({
toolbar: {
@ -125,10 +140,13 @@ export default {
return toolbarIcons;
},
blockWidth() {
return blockWidth;
return blockWidth * this.blockSizeMultiplier;
},
blockHeight() {
return blockHeight;
return blockHeight * this.blockSizeMultiplier;
},
blockSizeMultiplier() {
return this.$store.getters.blockSizeMultiplier
},
toolbarState() {
return this.$store.getters.toolbarState;
@ -160,6 +178,12 @@ export default {
draggable() {
return this.toolbarState.draggable;
},
gridView() {
return this.toolbarState.gridView;
},
updateBrush() {
return this.toolbarState.updateBrush;
},
},
watch: {},
methods: {

View File

@ -23,12 +23,18 @@
</style>
<script>
import { mircColours99, blockWidth, blockHeight, cyrb53, getBlocksWidth, filterNullBlocks } from "../../ascii";
import { mircColours99, blockWidth, blockHeight, cyrb53, getBlocksWidth, filterNullBlocks, checkVisible } from "../../ascii";
export default {
name: "BrushCanvas",
created() {
window.addEventListener('load', () => {
// Fixes the font on load issue
this.delayRedrawCanvas();
})
},
mounted() {
this.ctx = this.canvasRef.getContext("2d");
this.ctx = this.$refs[this.canvasName].getContext("2d");
this.delayRedrawCanvas();
},
props: {
@ -43,6 +49,15 @@ export default {
redraw: true,
}),
computed: {
blockWidth() {
return blockWidth * this.blockSizeMultiplier;
},
blockHeight() {
return blockHeight * this.blockSizeMultiplier;
},
blockSizeMultiplier() {
return this.$store.getters.blockSizeMultiplier
},
currentAscii() {
return this.$store.getters.currentAscii;
},
@ -93,8 +108,8 @@ export default {
},
blocksWidthHeight() {
return {
w: getBlocksWidth(this.getBlocks) * blockWidth,
h: this.getBlocks.length * blockHeight
w: getBlocksWidth(this.getBlocks) * this.blockWidth,
h: this.getBlocks.length * this.blockHeight
}
},
mircColours() {
@ -105,6 +120,9 @@ export default {
}
},
watch: {
blockSizeMultiplier() {
this.delayRedrawCanvas();
},
getBlocks() {
this.delayRedrawCanvas();
},
@ -168,10 +186,10 @@ export default {
this.ctx.fillStyle = this.mircColours[curBlock.bg];
this.ctx.fillRect(
x * blockWidth,
y * blockHeight,
blockWidth,
blockHeight
x * this.blockWidth,
y * this.blockHeight,
this.blockWidth,
this.blockHeight
);
}
@ -183,8 +201,8 @@ export default {
this.ctx.fillStyle = this.mircColours[curBlock.fg];
this.ctx.fillText(
curBlock.char,
x * blockWidth,
y * blockHeight + blockHeight - 3
x * this.blockWidth,
y * this.blockHeight + this.blockHeight - 3
);
}
}

View File

@ -1,55 +1,82 @@
<template>
<div>
<div class="flex">
<div class="w-1/2">
<t-input
type="number"
name="width"
v-model="brushSizeWidthInput"
min="1"
:max="maxBrushSize"
/>
</div>
<vue-draggable-resizable
@dragstop="onDragStop"
@resizestop="onResize"
:grid="[blockWidth, blockHeight]"
:min-width="blockWidth * 20"
:max-width="blockWidth * 80"
:min-height="blockHeight * 20"
:max-height="blockHeight * 80"
:w="brushPreviewState.w"
:h="brushPreviewState.h"
:x="brushPreviewState.x"
:y="brushPreviewState.y"
:draggable="canDrag"
>
<t-card class="h-full">
<div class="flex w-full">
<div class="w-1/2">
<t-input
type="number"
name="width"
v-model="brushSizeWidthInput"
min="1"
:max="maxBrushSize"
/>
</div>
<div class="w-1/2">
<t-input
type="number"
name="height"
v-model="brushSizeHeightInput"
min="1"
:max="maxBrushSize"
/>
</div>
</div>
<div class="w-1/2">
<t-input
type="number"
name="height"
v-model="brushSizeHeightInput"
min="1"
:max="maxBrushSize"
/>
</div>
</div>
<div class="flex">
<label class="block">
<t-radio
name="options"
value="square"
checked
v-model="brushSizeTypeInput"
/>
<span class="text-sm">Square</span>
</label>
<div class="flex w-full">
<label class="block">
<t-radio
name="options"
value="square"
checked
v-model="brushSizeTypeInput"
/>
<span class="text-sm">Square</span>
</label>
<label class="block">
<t-radio name="options" value="circle" v-model="brushSizeTypeInput" />
<span class="text-sm">Circle</span>
</label>
<label class="block">
<t-radio
name="options"
value="circle"
v-model="brushSizeTypeInput"
/>
<span class="text-sm">Circle</span>
</label>
<label class="block">
<t-radio name="options" value="cross" v-model="brushSizeTypeInput" />
<span class="text-sm">Cross</span>
</label>
</div>
<label class="block">
<t-radio
name="options"
value="cross"
v-model="brushSizeTypeInput"
/>
<span class="text-sm">Cross</span>
</label>
</div>
<MainBrushCanvas />
<div @mouseenter="canDrag = false" @mouseleave="canDrag = true">
<MainBrushCanvas />
</div>
</t-card>
</vue-draggable-resizable>
</div>
</template>
<script>
import { emptyBlock, maxBrushSize } from "../../ascii";
import { emptyBlock, maxBrushSize, blockWidth, blockHeight } from "../../ascii";
import MainBrushCanvas from "./MainBrushCanvas.vue";
export default {
@ -57,21 +84,52 @@ export default {
components: {
MainBrushCanvas,
},
mounted() {
created() {
if (this.brushBlocksEmpty) {
this.createBlocks();
this.brushSizeWidthInput = this.brushSizeWidth;
this.brushSizeHeightInput = this.brushSizeHeight;
this.brushSizeTypeInput = this.brushSizeType;
this.createBlocks();
}
this.panel.x = this.brushPreviewState.x;
this.panel.y = this.brushPreviewState.y;
this.panel.w = this.brushPreviewState.w;
this.panel.h = this.brushPreviewState.h;
},
data: () => ({
canDrag: true,
blocks: [],
brushSizeHeightInput: 1,
brushSizeWidthInput: 1,
brushSizeTypeInput: "square",
panel: {
w: 0,
h: 0,
x: 100,
y: 100,
visible: true,
},
}),
computed: {
blockWidth() {
return blockWidth * this.blockSizeMultiplier;
},
blockHeight() {
return blockHeight * this.blockSizeMultiplier;
},
blockSizeMultiplier() {
return this.$store.getters.blockSizeMultiplier;
},
canFg() {
return this.$store.getters.isTargettingFg;
},
canBg() {
return this.$store.getters.isTargettingBg;
},
canText() {
return this.$store.getters.isTargettingChar;
},
currentFg() {
return this.$store.getters.currentFg;
},
@ -81,6 +139,9 @@ export default {
currentChar() {
return this.$store.getters.currentChar;
},
toolbarState() {
return this.$store.getters.toolbarState;
},
brushSizeHeight() {
return this.$store.getters.brushSizeHeight;
},
@ -100,8 +161,14 @@ export default {
return this.brushBlocks.length === 0;
},
maxBrushSize() {
return maxBrushSize
}
return maxBrushSize;
},
brushPreviewState() {
return this.$store.getters.brushPreviewState;
},
updateBrush() {
return this.toolbarState.updateBrush;
},
},
watch: {
brushSizeWidth() {
@ -128,9 +195,40 @@ export default {
this.createBlocks();
}
},
canFg(val, old) {
if (val !== old && this.updateBrush) {
this.createBlocks();
}
},
canBg(val, old) {
if (val !== old && this.updateBrush) {
this.createBlocks();
}
},
canText(val, old) {
if (val !== old && this.updateBrush) {
this.createBlocks();
}
},
currentFg(val, old) {
if (val !== old && this.updateBrush) {
this.createBlocks();
}
},
currentBg(val, old) {
if (val !== old && this.updateBrush) {
this.createBlocks();
}
},
currentChar(val, old) {
if (val !== old && this.updateBrush) {
this.createBlocks();
}
},
brushBlocks() {
this.$store.commit("pushBrushHistory", this.brushBlocks);
},
},
methods: {
updateBrushSize() {
@ -160,7 +258,7 @@ export default {
const middleY = Math.floor(brushHeight / 2);
const middleX = Math.floor(brushWidth / 2);
let yModifier = 0;
// Recreate 2d array for preview
for (y = 0; y < brushHeight; y++) {
this.blocks[y] = [];
@ -228,6 +326,20 @@ export default {
this.$store.commit("brushBlocks", this.blocks);
},
onResize(x, y, w, h) {
this.panel.x = x;
this.panel.y = y;
this.panel.w = w;
this.panel.h = h;
this.$store.commit("changeBrushPreviewState", this.panel);
},
onDragStop(x, y) {
this.panel.x = x;
this.panel.y = y;
this.$store.commit("changeBrushPreviewState", this.panel);
},
},
};
</script>

View File

@ -7,7 +7,7 @@ import { mircColours99, toolbarIcons, maxBrushSize } from '../../ascii';
export default {
name: 'KeyboardShortcuts',
mounted() {
created() {
const thisIs = this;
this.keyListener = function (e) {
// Stop blocking input when modals are open
@ -69,7 +69,7 @@ export default {
// Show / hide grid view
if (e.key === "g" && ctrlKey) {
this.$store.commit("toggleGridView", !this.gridView);
this.$store.commit("toggleGridView", !this.toolbarState.gridView);
}
// Show / hide brush library
@ -261,9 +261,6 @@ export default {
brushLibraryState() {
return this.$store.getters.brushLibraryState;
},
gridView() {
return this.$store.getters.gridView;
},
maxBrushSize() {
return maxBrushSize;
},

View File

@ -0,0 +1,162 @@
<template>
<div>
<t-card class="h-full">
<t-button
type="button"
class="block w-full border-gray-200 bg-gray-500 p-2 m-2"
@click="addLayer()"
>Add Layer</t-button
>
<hr />
<div class="w-full bg-white rounded-lg shadow">
<ul class="divide-y-2 divide-gray-100">
<li
:class="`p-1 ${selectedLayerClass(key)}`"
v-for="(layer, key) in currentAsciiLayers"
:key="key"
>
<div class="flex">
<div class="w-12">
<t-button
type="button"
class="rounded-xl"
@click="toggleLayer(key)"
:disabled="!canToggleLayer && lastVisible"
><font-awesome-icon
:icon="[
'fas',
layer.visible ? 'eye' : 'eye-slash',
]" /></t-button
><br />
<t-button
type="button"
class="rounded-xl"
@click="removeLayer(key)"
:disabled="!canToggleLayer"
><font-awesome-icon :icon="['fas', 'trash']"
/></t-button>
</div>
<div class="w-full" @click="changeLayer(key)">
<div class="flex text-right">
<div class="w-full">
<t-card class="w-full">
{{ layer.label }}
</t-card>
</div>
<div class="w-5">
<t-button
type="button"
class="rounded-xl"
@click="upLayer(key)"
:disabled="!canToggleLayer"
><font-awesome-icon
:icon="['fas', 'chevron-circle-up']" /></t-button
><br />
<t-button
type="button"
class="rounded-xl"
@click="downLayer(key)"
:disabled="!canToggleLayer"
><font-awesome-icon
:icon="['fas', 'chevron-circle-down']"
/></t-button>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</t-card>
</div>
</template>
<script>
export default {
name: "Layers",
created() {},
data: () => ({
dragging: false,
}),
computed: {
toolbarState() {
return this.$store.getters.toolbarState;
},
currentAsciiLayers() {
return this.$store.getters.currentAsciiLayers;
},
selectedLayer() {
return this.$store.getters.selectedLayer;
},
canToggleLayer() {
return this.currentAsciiLayers.length !== 1;
// We want to avoid hiding all the layers, so if there's only one
// visible left, we have to disable the buttons
},
lastVisible() {
let visibleCount = 0;
for (let i = 0; i < this.currentAsciiLayers.length; i++) {
if (this.currentAsciiLayers[i].visible) {
visibleCount++;
}
}
return visibleCount === 1;
},
},
methods: {
selectedLayerClass(key) {
// Invisble layers are red
if (
this.currentAsciiLayers[key].visible === false &&
key === this.selectedLayer
) {
if (this.currentAsciiLayers[key + 1]) {
this.changeLayer(key + 1);
} else if (this.currentAsciiLayers[key - 1]) {
this.changeLayer(key - 1);
}
}
if (!this.currentAsciiLayers[key].visible) {
return "bg-red-200";
}
// Selected layers blue
if (key === this.selectedLayer) {
return "bg-blue-200";
}
// Otherwise gray
return "bg-gray-200";
},
addLayer() {
this.$store.commit("addLayer");
},
changeLayer(key) {
this.$store.commit("changeLayer", key);
},
toggleLayer(key) {
if (!this.lastVisible || key !== this.selectedLayer) {
this.$store.commit("toggleLayer", key);
}
},
upLayer(key) {
this.$store.commit("upLayer", key);
},
downLayer(key) {
this.$store.commit("downLayer", key);
},
removeLayer(key) {
this.$store.commit("removeLayer", key);
},
},
};
</script>

View File

@ -43,9 +43,14 @@ import {
export default {
name: "MainBrushCanvas",
created() {
window.addEventListener('load', () => {
// Fixes the font on load issue
this.delayRedrawCanvas();
})
},
mounted() {
this.ctx = this.canvasRef.getContext("2d");
this.delayRedrawCanvas();
},
data: () => ({
ctx: null,
@ -56,6 +61,15 @@ export default {
y: 0,
}),
computed: {
blockWidth() {
return blockWidth * this.blockSizeMultiplier;
},
blockHeight() {
return blockHeight * this.blockSizeMultiplier;
},
blockSizeMultiplier() {
return this.$store.getters.blockSizeMultiplier
},
currentAscii() {
return this.$store.getters.currentAscii;
},
@ -148,6 +162,9 @@ export default {
currentChar() {
this.delayRedrawCanvas();
},
blockSizeMultiplier() {
this.delayRedrawCanvas();
},
},
methods: {
getBlocksWidth(blocks) {
@ -156,6 +173,7 @@ export default {
filterNullBlocks(blocks) {
return filterNullBlocks(blocks);
},
drawPreview() {
this.ctx.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height);
this.ctx.fillStyle = this.mircColours[1];

View File

@ -11,7 +11,11 @@ import {
faSync,
faSave,
faTrash,
faFill
faFill,
faChevronCircleUp,
faChevronCircleDown,
faEye,
faEyeSlash
} from '@fortawesome/free-solid-svg-icons';
import {
@ -19,4 +23,4 @@ import {
} from '@fortawesome/free-regular-svg-icons';
library.add(faMousePointer, faSquare, faFont, faFillDrip, faPaintBrush, faEyeDropper, faEraser,
faSync, faSave, faTrash, faFill);
faSync, faSave, faTrash, faFill, faChevronCircleUp, faChevronCircleDown, faEye, faEyeSlash);

View File

@ -9,6 +9,7 @@ import store from './store';
import Dashboard from './Dashboard.vue';
import Toasted from 'vue-toasted';
Vue.config.productionTip = false;
import {

View File

@ -6,7 +6,9 @@ import {
blockWidth,
blockHeight,
cyrb53,
getBlocksWidth
getBlocksWidth,
create2DArray,
emptyBlock
} from "../ascii";
Vue.use(Vuex);
@ -25,13 +27,11 @@ export default new Vuex.Store({
// The various options of ASCIIBIRD will eventually
// end up in its own modal I guess
options: {
canvasRedrawSpeed: 50,
defaultBg: 1,
defaultFg: 0,
},
// Current tab user is viewing
tab: 0,
gridView: false,
// asciibirdMeta holds all of the ASCII information for all the tabs
asciibirdMeta: [],
toolbarState: {
@ -56,15 +56,17 @@ export default new Vuex.Store({
mirrorY: false,
x: blockWidth * 2,
y: blockHeight * 2,
h: blockHeight * 39,
h: blockHeight * 19,
w: blockWidth * 25,
draggable: true,
updateBrush: true,
gridView: false,
},
debugPanelState: {
x: 936,
y: 39,
h: 260,
w: 320,
x: blockWidth * 130,
y: blockHeight * 2,
h: blockHeight * 50,
w: blockWidth * 25,
visible: false,
},
blockSizeMultiplier: 1,
@ -76,10 +78,17 @@ export default new Vuex.Store({
x: blockWidth * 130,
y: blockHeight * 2,
h: blockHeight * 50,
w: blockWidth * 25,
w: blockWidth * 35,
visible: true,
tab: 0,
},
brushPreviewState: {
x: blockWidth * 2,
y: blockHeight * 22,
h: blockHeight * 19,
w: blockWidth * 25,
visible: true,
},
},
mutations: {
changeState(state, payload) {
@ -98,6 +107,9 @@ export default new Vuex.Store({
changeBrushLibraryState(state, payload) {
state.brushLibraryState = payload;
},
changeBrushPreviewState(state, payload) {
state.brushPreviewState = payload;
},
toggleBrushLibrary(state, payload) {
state.brushLibraryState.visible = payload;
},
@ -171,12 +183,104 @@ export default new Vuex.Store({
state.asciibirdMeta[state.tab].history.push(state.asciibirdMeta[state.tab].blocks);
}
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(payload));
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
tempLayers[state.asciibirdMeta[state.tab].selectedLayer].data = payload
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
tempLayers));
state.asciibirdMeta[state.tab].redo = [];
},
//
// LAYERS
//
addLayer(state) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
let newBlocksArray = create2DArray(state.asciibirdMeta[state.tab].height);
// Push all the default ASCII blocks
for (let x = 0; x < state.asciibirdMeta[state.tab].width; x++) {
for (let y = 0; y < state.asciibirdMeta[state.tab].height; y++) {
newBlocksArray[y].push({
...emptyBlock,
});
}
}
tempLayers.unshift({
label: 'Layer ' + Number.parseInt(tempLayers.length),
visible: true,
data: newBlocksArray,
})
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
tempLayers));
},
changeLayer(state, payload) {
state.asciibirdMeta[state.tab].selectedLayer = payload
},
toggleLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
tempLayers[payload].visible = !tempLayers[payload].visible
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
tempLayers));
},
removeLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
if (tempLayers.length > 1) {
tempLayers.splice(payload, 1)
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
tempLayers));
}
},
downLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
if (tempLayers[payload + 1]) {
let swap1 = tempLayers[payload + 1];
let swap = tempLayers[payload];
tempLayers[payload + 1] = swap
tempLayers[payload] = swap1
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
tempLayers));
}
},
upLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
if (tempLayers[payload - 1]) {
let swap1 = tempLayers[payload - 1];
let swap = tempLayers[payload];
tempLayers[payload - 1] = swap
tempLayers[payload] = swap1
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
tempLayers));
}
},
// ASCII
updateAscii(state, payload) {
state.asciibirdMeta[state.tab] = payload;
},
// BLOCKS
undoBlocks(state) {
if (state.asciibirdMeta[state.tab].history.length > 1) {
state.asciibirdMeta[state.tab].redo.push(state.asciibirdMeta[state.tab].blocks);
@ -190,6 +294,10 @@ export default new Vuex.Store({
state.asciibirdMeta[state.tab].history.push(next);
}
},
//
// Toolbar
//
updateBrushSize(state, payload) {
state.toolbarState.brushSizeHeight = payload.brushSizeHeight;
state.toolbarState.brushSizeWidth = payload.brushSizeWidth;
@ -202,11 +310,12 @@ export default new Vuex.Store({
state.selectBlocks = LZString.compressToUTF16(JSON.stringify(payload));
},
toggleGridView(state, payload) {
state.gridView = payload;
state.toolbarState.gridView = payload;
},
//
// Brush stuff
//
toggleUpdateBrush(state, payload) {
state.toolbarState.updateBrush = payload;
},
flipRotateBlocks(state, payload) {
let tempBlocks = JSON.parse(LZString.decompressFromUTF16(state.brushBlocks))
let parsedBlocks = [];
@ -339,8 +448,19 @@ export default new Vuex.Store({
currentChar: (state) => state.toolbarState.selectedChar,
currentTab: (state) => state.tab,
currentAscii: (state) => state.asciibirdMeta[state.tab] ?? false,
currentAsciiBlocks: (state) => JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[
state.tab].blocks)) || [],
currentAsciiBlocks: (state) => {
let blocks = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[
state.tab].blocks));
return blocks[state.asciibirdMeta[state.tab].selectedLayer].data
},
currentAsciiLayers: (state) => {
let blocks = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[
state.tab].blocks));
return blocks
},
selectedLayer: (state) => state.asciibirdMeta[state.tab].selectedLayer,
asciibirdMeta: (state) => state.asciibirdMeta,
nextTabValue: (state) => state.asciibirdMeta.length,
brushSizeHeight: (state) => state.toolbarState.brushSizeHeight,
@ -349,8 +469,8 @@ export default new Vuex.Store({
blockSizeMultiplier: (state) => state.blockSizeMultiplier,
brushHistory: (state) => state.brushHistory,
brushLibrary: (state) => state.brushLibrary,
gridView: (state) => state.gridView,
brushLibraryState: (state) => state.brushLibraryState,
brushPreviewState: (state) => state.brushPreviewState,
brushBlocks: (state) => JSON.parse(LZString.decompressFromUTF16(state.brushBlocks)) || [],
selectBlocks: (state) => JSON.parse(LZString.decompressFromUTF16(state.selectBlocks)) || [],
isModalOpen: (state) => {

View File

@ -7,7 +7,7 @@
>
<vue-draggable-resizable
style="z-index: 5"
:grid="[currentAscii.blockWidth, currentAscii.blockHeight]"
:grid="[blockWidth, blockHeight]"
:w="canvas.width"
:h="canvas.height"
:draggable="isDefault"
@ -16,23 +16,20 @@
:x="currentAscii.x"
:y="currentAscii.y"
>
<canvas
ref="canvastools"
id="canvastools"
class="canvastools"
:width="canvas.width"
:height="canvas.height"
@mousemove="canvasMouseMove"
@mousedown="canvasMouseDown"
@mouseup="canvasMouseUp"
/>
<canvas
ref="canvas"
id="canvas"
class="canvas"
:width="canvas.width"
:height="canvas.height"
/>
<canvas
ref="canvastools"
id="canvastools"
class="canvastools"
:width="canvas.width"
:height="canvas.height"
@mousemove="canvasMouseMove"
@mousedown="canvasMouseDown"
@mouseup="canvasMouseUp"
@ -73,15 +70,23 @@ import {
fillNullBlocks,
emptyBlock,
getBlocksWidth,
checkVisible,
} from "../ascii";
export default {
name: "Editor",
mounted() {
if (this.currentAsciiBlocks) {
this.ctx = this.$refs.canvas.getContext("2d");
this.toolCtx = this.$refs.canvastools.getContext("2d");
this.ctx = this.canvasRef.getContext("2d");
this.toolCtx = this.$refs.canvastools.getContext("2d");
this.delayRedrawCanvas();
},
created() {
window.addEventListener("load", () => {
// Fixes the font on load issue
this.delayRedrawCanvas();
});
if (this.currentAsciiBlocks) {
this.canvas.width = this.currentAscii.width * blockWidth;
this.canvas.height = this.currentAscii.height * blockHeight;
@ -204,8 +209,6 @@ export default {
return;
}
// Choose FG or BG with Keyboard
if (
Number.parseInt(e.key) >= 0 &&
@ -439,6 +442,7 @@ export default {
}
};
document.removeEventListener("keydown", this.keyListener.bind(this));
document.addEventListener("keydown", this.keyListener.bind(this));
}
},
@ -451,6 +455,7 @@ export default {
},
x: 0, // Ascii X and Y
y: 0, // Ascii X and Y
top: false,
redraw: true, // Used to limit canvas redraw
canTool: false,
textEditing: {
@ -468,6 +473,18 @@ export default {
selectedBlocks: [],
}),
computed: {
canvasRef() {
return this.$refs.canvas;
},
blockWidth() {
return blockWidth * this.blockSizeMultiplier;
},
blockHeight() {
return blockHeight * this.blockSizeMultiplier;
},
blockSizeMultiplier() {
return this.$store.getters.blockSizeMultiplier;
},
isModalOpen() {
return this.$store.getters.isModalOpen;
},
@ -486,6 +503,12 @@ export default {
currentAsciiBlocks() {
return this.$store.getters.currentAsciiBlocks;
},
currentAsciiLayers() {
return this.$store.getters.currentAsciiLayers;
},
currentAsciiLayerBlocks() {
return this.currentAsciiLayers[this.currentAscii.selectedLayer].data;
},
currentTool() {
return toolbarIcons[this.$store.getters.currentTool];
},
@ -569,7 +592,7 @@ export default {
return this.$store.getters.brushLibraryState;
},
gridView() {
return this.$store.getters.gridView;
return this.toolbarState.gridView;
},
asciiBlockAtXy() {
return this.currentAsciiBlocks[this.y] &&
@ -599,6 +622,13 @@ export default {
document.title = `asciibird - ${this.currentAscii.title}`;
}
},
currentAsciiLayers() {
this.delayRedrawCanvas();
},
// currentAsciiBlocks() {
// this.delayRedrawCanvas();
// },
currentTool() {
switch (this.currentTool.name) {
case "default":
@ -631,8 +661,14 @@ export default {
this.drawBrush();
}
},
blockSizeMultiplier() {
this.delayRedrawCanvas();
},
},
methods: {
checkVisible(top) {
return checkVisible(top, top - this.blockHeight);
},
undo() {
this.$store.commit("undoBlocks");
this.delayRedrawCanvas();
@ -672,13 +708,15 @@ export default {
}
},
redrawCanvas() {
if (this.currentAsciiBlocks.length) {
if (this.currentAsciiLayers.length) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Position of the meta array
let x = 0;
let y = 0;
let i = 0;
// Draws the actual rectangle
let canvasX = 0;
let canvasY = 0;
@ -690,10 +728,41 @@ export default {
for (y = 0; y < this.currentAscii.height + 1; y++) {
canvasY = blockHeight * y;
for (x = 0; x < this.currentAscii.width + 1; x++) {
if (this.currentAsciiBlocks[y] && this.currentAsciiBlocks[y][x]) {
curBlock = { ...this.currentAsciiBlocks[y][x] };
// Experimental code to not rows blocks off screen
// if (this.top !== false && !this.checkVisible(this.top + canvasY)) {
// continue;
// }
for (x = 0; x < this.currentAscii.width + 1; x++) {
// Loop layers
for (i = this.currentAsciiLayers.length - 1; i >= 0; i--) {
// If this layer is invisble, skip it
if (!this.currentAsciiLayers[i].visible) {
continue;
}
// If this block on this layer has no data, skip to next row
if (
this.currentAsciiLayers[i] &&
this.currentAsciiLayers[i].data &&
this.currentAsciiLayers[i].data[y] &&
this.currentAsciiLayers[i].data[y][x] &&
i > 0 &&
JSON.stringify(this.currentAsciiLayers[i].data[y][x]) ===
JSON.stringify(emptyBlock)
) {
continue;
} else if (
// Otherwise if we are on the very first layer we need to render it
this.currentAsciiLayers[i] &&
this.currentAsciiLayers[i].data &&
this.currentAsciiLayers[i].data[y] &&
this.currentAsciiLayers[i].data[y][x]
) {
curBlock = { ...this.currentAsciiLayers[i].data[y][x] };
}
// this.currentAsciiBlocks[y][x] = { ... curBlock}
canvasX = blockWidth * x;
if (this.gridView) {
@ -717,10 +786,12 @@ export default {
this.ctx.fillText(
curBlock.char,
canvasX ,
canvasX,
canvasY + blockHeight - 3
);
}
break;
}
}
}
@ -735,6 +806,7 @@ export default {
canvasBlockWidth
);
this.top = top;
this.canvas.width = width;
this.canvas.height = height;
@ -749,7 +821,13 @@ export default {
},
onCavasDragStop(x, y) {
// Update left and top in panel
this.top = y;
this.$store.commit("changeAsciiCanvasState", { x, y });
this.delayRedrawCanvas();
},
onCanvasDrag(x, y) {
this.top = y;
this.delayRedrawCanvas();
},
canvasKeyDown(char) {
if (this.isTextEditing) {
@ -1302,7 +1380,7 @@ export default {
}
}
this.toolCtx.font = "Hack 13px"
this.toolCtx.font = "Hack 13px";
// We always have a Y array
const brushDiffY = Math.floor(this.brushBlocks.length / 2) * blockHeight;
@ -1488,7 +1566,7 @@ export default {
if (this.mirrorX) {
this.toolCtx.fillText(
brushBlock.char,
(asciiWidth - arrayX) * blockWidth + 3,
(asciiWidth - arrayX) * blockWidth + 3,
brushY + blockHeight - 4
);
}
@ -1496,14 +1574,14 @@ export default {
if (this.mirrorY) {
this.toolCtx.fillText(
brushBlock.char,
brushX + 3,
brushX + 3,
(asciiHeight - arrayY) * blockHeight + 10
);
}
if (this.mirrorY && this.mirrorX) {
this.toolCtx.fillText(
brushBlock.char,
(asciiWidth - arrayX) * blockWidth + 3,
(asciiWidth - arrayX) * blockWidth + 3,
(asciiHeight - arrayY) * blockHeight + 10
);
}
@ -1707,7 +1785,7 @@ export default {
// If the newColor is same as the existing
// Then return the original image.
if (current === newColor && !eraser) {
return
return;
}
this.fillTool(this.currentAsciiBlocks, this.y, this.x, current, eraser);

View File

@ -8480,6 +8480,11 @@ sort-keys@^1.0.0:
dependencies:
is-plain-obj "^1.0.0"
sortablejs@1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
source-list-map@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@ -9502,6 +9507,13 @@ vue@^2.6.11:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
vuedraggable@^2.24.3:
version "2.24.3"
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.24.3.tgz#43c93849b746a24ce503e123d5b259c701ba0d19"
integrity sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==
dependencies:
sortablejs "1.10.2"
vuex-persist@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/vuex-persist/-/vuex-persist-3.1.3.tgz#518c722a2ca3026bcee5732f99d24f75cee0f3b6"