scene designer
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item to="/game-objects/add">Нов игрови обект</v-list-item>
|
||||
<v-list-item>Нов сценарий</v-list-item>
|
||||
<v-list-item to="/scenarios/add">Нов сценарий</v-list-item>
|
||||
<v-list-item to="/games/add">Нова игра</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
@@ -21,7 +21,7 @@
|
||||
<v-divider></v-divider>
|
||||
<v-list nav>
|
||||
<v-list-item prepend-icon="mdi-database" to="/game-objects/list" :title="$l.gameObjects"></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-receipt-text-edit-outline" :title="$l.gameScenarios"></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-receipt-text-edit-outline" to="/scenarios/list" :title="$l.gameScenarios"></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-cogs" :title="$l.gameRules"></v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item prepend-icon="mdi-controller" :title="$l.games" to="/games/list"></v-list-item>
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div class="container my-3">
|
||||
<v-btn-toggle variant="tonal" density="compact" class="mx-auto" v-model="listMode" color="blue">
|
||||
<v-btn class="text-none" value="scene"><v-icon>mdi-panorama-outline</v-icon><span>{{ $l.addScene }}</span></v-btn>
|
||||
<v-btn class="text-none" value="object"><v-icon>mdi-bird</v-icon><span>{{ $l.addScene }}</span></v-btn>
|
||||
<v-btn class="text-none" value="task"><v-icon>mdi-checkbox-marked-circle-plus-outline</v-icon><span>{{ $l.addScene }}</span></v-btn>
|
||||
</v-btn-toggle>
|
||||
<div @wheel="onWheel" @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onDrag"
|
||||
:class="`svg-container ${mode}`" ref="svgContainer">
|
||||
<svg>
|
||||
|
||||
</svg>
|
||||
</div>
|
||||
<v-navigation-drawer location="right">
|
||||
<svg-scene></svg-scene>
|
||||
</v-navigation-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SvgScene from './SvgScene.vue';
|
||||
|
||||
const components = {
|
||||
SvgScene
|
||||
}
|
||||
export default {
|
||||
components: { SvgScene },
|
||||
props:{
|
||||
modelValue: Object
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
listMode: 'select'
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
object:()=>this.modelValue,
|
||||
zoom:{
|
||||
get(){
|
||||
return 1 / this.scale;
|
||||
},
|
||||
set(v){
|
||||
this.rescale(1 / v);
|
||||
}
|
||||
},
|
||||
mode(){
|
||||
return this.listMode[0];
|
||||
},
|
||||
components(){
|
||||
return components;
|
||||
},
|
||||
},
|
||||
methods:{
|
||||
rescale(scale, e){
|
||||
let oldScale = this.scale;
|
||||
if (!e){
|
||||
this.scale = scale;
|
||||
this.offset.x += (oldScale - this.scale) * (this.$refs.svgContainer.offsetWidth/2);
|
||||
this.offset.y += (oldScale - this.scale) * (this.$refs.svgContainer.offsetHeight/2);
|
||||
}else{
|
||||
this.scale *= (1 + Math.sign(e.deltaY) / 10);
|
||||
let oo = {
|
||||
x: (oldScale - this.scale) * e.offsetX,
|
||||
y: (oldScale - this.scale) * e.offsetY
|
||||
};
|
||||
if (this.target){
|
||||
this.retarget(e)
|
||||
}
|
||||
this.offset.x += oo.x;
|
||||
this.offset.y += oo.y
|
||||
}
|
||||
document.documentElement.style.setProperty('--svg-scale', this.scale);
|
||||
},
|
||||
retarget(e){
|
||||
if (this.target.delta){
|
||||
this.retargetDelta(e, this.target)
|
||||
}else{
|
||||
this.retargetAbsolute(e, this.target)
|
||||
}
|
||||
},
|
||||
retargetDelta(e, target){
|
||||
let p = {
|
||||
x: Utils.round(e.movementX*this.scale, 0),
|
||||
y: Utils.round(e.movementY*this.scale, 0)
|
||||
}
|
||||
target.attrs.forEach(a=>{
|
||||
if (Array.isArray(a)){
|
||||
a[0] = p.x;
|
||||
a[1] = p.y;
|
||||
}else if (a.startsWith('x')){
|
||||
this.target.target[a] += p.x;
|
||||
}else if (a.startsWith('y')){
|
||||
this.target.target[a] += p.y;
|
||||
}
|
||||
})
|
||||
},
|
||||
retargetAbsolute(e, target){
|
||||
let p = {
|
||||
x: Utils.round(this.vb.x + e.offsetX * this.scale),
|
||||
y: Utils.round(this.vb.y + e.offsetY * this.scale)
|
||||
}
|
||||
target.attrs.forEach(a=>{
|
||||
if (Array.isArray(a)){
|
||||
a[0] = p.x;
|
||||
a[1] = p.y;
|
||||
}else if (a.startsWith('x')){
|
||||
this.target.target[a] = p.x;
|
||||
}else if (a.startsWith('y')){
|
||||
this.target.target[a] = p.y;
|
||||
}
|
||||
})
|
||||
},
|
||||
onWheel(e){
|
||||
this.rescale(null, e);
|
||||
},
|
||||
onDrag(e){
|
||||
if (this.mousedown?.button == 0 || this.modeStep > 0) {
|
||||
if (this.mode == 'move'){
|
||||
let p = {
|
||||
x: Utils.round(e.movementX*this.scale, 0),
|
||||
y: Utils.round(e.movementY*this.scale, 0)
|
||||
}
|
||||
this.selectedItem.forEach(i=>{
|
||||
let mf = components[i.name].modifiers;
|
||||
mf.filter(m=>m.match(/^x[0-9]+$/)).forEach(x=>i.data[x]+= p.x);
|
||||
mf.filter(m=>m.match(/^y[0-9]+$/)).forEach(y=>i.data[y]+= p.y);
|
||||
})
|
||||
}else if (this.target) {
|
||||
this.retarget(e)
|
||||
}
|
||||
}
|
||||
if (e.shiftKey ||
|
||||
this.mousedown?.button == 1 ||
|
||||
(this.mode == 'pan' && this.mousedown?.button == 0) ||
|
||||
(this.mode == 'default' && this.mousedown?.button == 0 && !this.target)
|
||||
){
|
||||
this.offset.x -= e.movementX*this.scale;
|
||||
this.offset.y -= e.movementY*this.scale;
|
||||
}
|
||||
//console.log(e);
|
||||
},
|
||||
onMouseDown(e){
|
||||
this.mousedown = { button: e.button };
|
||||
//console.log(e, this.mode, this.modeStep)
|
||||
if (e.button == 0 && !['default', 'move', 'pan'].includes( this.mode )){
|
||||
let cs;
|
||||
if (this.mode == 'select'){
|
||||
//console.log('selecting')
|
||||
cs = [['x1', 'y1'], ['x2', 'y2']];
|
||||
if (this.modeStep == 0){
|
||||
this.target = {
|
||||
target: this.selector,
|
||||
attrs: []
|
||||
}
|
||||
}
|
||||
}else{
|
||||
cs = this.components[this.mode].steps;
|
||||
//console.log(cs);
|
||||
if (this.modeStep == 0){
|
||||
this.target = {
|
||||
target: {},
|
||||
attrs: []
|
||||
}
|
||||
let id, nid = 1;
|
||||
do {
|
||||
id = `${this.components[this.mode].name}-${nid++}`
|
||||
}while (this.items.find(i=>i.id == id))
|
||||
this.items.push({
|
||||
name: this.mode,
|
||||
data: this.target.target,
|
||||
visible: true,
|
||||
id, title: id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let p = {
|
||||
x: Utils.round(this.vb.x + e.offsetX * this.scale),
|
||||
y: Utils.round(this.vb.y + e.offsetY * this.scale)
|
||||
}
|
||||
|
||||
for (let i = this.modeStep + 1; i <= cs.length; i++){
|
||||
this.target.target[cs[i-1][0]] = p.x;
|
||||
this.target.target[cs[i-1][1]] = p.y;
|
||||
}
|
||||
|
||||
this.modeStep++;
|
||||
if (this.modeStep >= cs.length){
|
||||
this.modeStep = 0;
|
||||
if (this.mode == 'select'){
|
||||
this.select();
|
||||
this.selector.x1 = this.selector.y1 = this.selector.x2 = this.selector.y2 = 0;
|
||||
}
|
||||
}
|
||||
if (this.modeStep ){
|
||||
this.target.attrs[0] = cs[this.modeStep][0];
|
||||
this.target.attrs[1] = cs[this.modeStep][1];
|
||||
}
|
||||
}
|
||||
},
|
||||
onMouseUp(){
|
||||
this.mousedown = false;
|
||||
if (this.mode == 'default' && !this.target){
|
||||
this.selectedItem = [];
|
||||
}
|
||||
if (this.modeStep == 0){
|
||||
this.target = null;
|
||||
}
|
||||
},
|
||||
setTarget(t, item){
|
||||
this.target = t;
|
||||
this.selectedItem = [item]
|
||||
},
|
||||
async save(){
|
||||
let imageData = await Utils.blobToBase64(await (await fetch(this.imageTarget)).blob());
|
||||
//console.log(imageData)
|
||||
let jsonData = {
|
||||
items: this.items,
|
||||
viewBox: this.viewBox,
|
||||
offset: this.offset,
|
||||
zoom: this.zoom,
|
||||
rotation: this.imgRotation,
|
||||
page: this.page,
|
||||
data: imageData
|
||||
};
|
||||
const link = document.createElement('a')
|
||||
const blob = new Blob([JSON.stringify(jsonData)], {type: 'application/json'});
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = "document.json"
|
||||
link.click();
|
||||
},
|
||||
select(){
|
||||
let r = Utils.adjustMinMax(this.selector);
|
||||
this.selectedItem = this.items.filter(i=>this.$refs['svg-'+i.id][0].intersect(r));
|
||||
},
|
||||
|
||||
async processImage(){
|
||||
this.processingImage = true;
|
||||
await this.$nextTick();
|
||||
let processor = new ImageProcessor(this.$refs.imageCanvas, this.img)
|
||||
let lines = processor.identifyLines(), iw = this.img.naturalWidth;
|
||||
lines.forEach((l, i)=>{
|
||||
this.items.push({
|
||||
name: 'SvgHorizontalLine', id: `aline-${i}`, visible: true, title: `ALine-${i}`,
|
||||
data: { x1:iw*.1, y1: l, x2: iw*.9}
|
||||
})
|
||||
})
|
||||
this.processingImage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.svg-container{
|
||||
svg{
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
image{
|
||||
clip-path: circle(50% at 50% 50%);
|
||||
}
|
||||
circle {
|
||||
stroke: rgb(var(--v-theme-primary));
|
||||
fill:rgba(255,255,255,.5);
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<circle :cx="65 + x" :cy="65 + y" :r="70"></circle>
|
||||
<image :href="src" :x="x" :y="y" height="130" width="130" preserveAspectRatio="xMidYMid slice"></image>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props:['src', 'x', 'y'],
|
||||
data(){
|
||||
return {
|
||||
target: null,
|
||||
img: null,
|
||||
size:{}
|
||||
}
|
||||
},
|
||||
created(){
|
||||
// this.img = new Image();
|
||||
// this.img.onload = ()=>{
|
||||
// this.size = {
|
||||
// w: this.img.naturalWidth,
|
||||
// h: this.img.naturalHeight,
|
||||
// a: this.img.naturalWidth / this.img.naturalHeight
|
||||
// }
|
||||
// }
|
||||
// this.img.src= this.target;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<teleport to="svg" defer>
|
||||
<g>
|
||||
<svg-avatar src="/asset/thumb/6.webp" :x="50" :y="50"></svg-avatar>
|
||||
</g>
|
||||
</teleport>
|
||||
<v-list density="compact" nav>
|
||||
<v-list-item prepend-icon="mdi-panorama-outline" :title="$l.addScene" value="scene"></v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SvgAvatar from './SvgAvatar.vue';
|
||||
export default {
|
||||
components: { SvgAvatar },
|
||||
data(){
|
||||
return {
|
||||
active: false
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.active = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user