贴图轴点
This commit is contained in:
1 parent
c041503004
commit
4d54c87681
13 files changed
+2294
-1248
No files matched your search
@@ -57,12 +57,64 @@
|
||||
</button>
|
||||
</div>
|
||||
<slot />
|
||||
<div v-if="supportsPivot" class="pivot-toggle-row">
|
||||
<div class="pivot-toggle-icon">◎</div>
|
||||
<div class="pivot-toggle-text">
|
||||
<div class="pivot-toggle-title">贴图轴点</div>
|
||||
<div class="pivot-toggle-sub" :title="pivotSummary">{{ pivotSummary }}</div>
|
||||
</div>
|
||||
<label class="pivot-switch" :title="pivotOpen ? '收起轴点调整' : '展开轴点调整'">
|
||||
<input v-model="pivotOpen" type="checkbox" />
|
||||
<span class="pivot-switch-ui"></span>
|
||||
</label>
|
||||
</div>
|
||||
<PivotEditor
|
||||
v-if="supportsPivot && pivotOpen"
|
||||
:preview-url="displayPreviewUrl"
|
||||
:anchor-x="resource.anchorX ?? 0.5"
|
||||
:anchor-y="resource.anchorY ?? 0.5"
|
||||
@update:anchorX="resource.anchorX = $event"
|
||||
@update:anchorY="resource.anchorY = $event"
|
||||
@fullscreen="openPivotFullscreen"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-if="pivotFullscreen" class="pivot-modal-backdrop" @pointerdown.self="closePivotFullscreen(false)">
|
||||
<div class="pivot-modal" role="dialog" aria-modal="true" aria-label="贴图旋转轴点大图精修">
|
||||
<div class="pivot-modal-head">
|
||||
<div class="pivot-modal-icon">◎</div>
|
||||
<div class="pivot-modal-info">
|
||||
<div class="pivot-modal-title">
|
||||
贴图旋转轴点大图精修
|
||||
<span class="pivot-modal-badge">{{ resource.textureName || defaultFile }}</span>
|
||||
</div>
|
||||
<div class="pivot-modal-desc">在下方贴图上任意点击或拖拽,精确设置粒子的旋转/摆动中心</div>
|
||||
</div>
|
||||
<button type="button" class="pivot-modal-close" title="关闭" @click="closePivotFullscreen(false)">✕</button>
|
||||
</div>
|
||||
<div class="pivot-modal-hint">支持 100% ~ 400% 放大,精确对准尖端或根部:</div>
|
||||
<PivotEditor
|
||||
large
|
||||
:preview-url="displayPreviewUrl"
|
||||
:anchor-x="draftX"
|
||||
:anchor-y="draftY"
|
||||
@update:anchorX="draftX = $event"
|
||||
@update:anchorY="draftY = $event"
|
||||
/>
|
||||
<div class="pivot-modal-actions">
|
||||
<button type="button" class="pivot-modal-cancel" @click="closePivotFullscreen(false)">取消</button>
|
||||
<button type="button" class="pivot-modal-confirm" @click="closePivotFullscreen(true)">确定并保存轴点</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import NumSlider from './NumSlider.vue'
|
||||
import PivotEditor from './PivotEditor.vue'
|
||||
|
||||
type CommonImageResource = {
|
||||
id: number
|
||||
@@ -73,6 +125,8 @@ type CommonImageResource = {
|
||||
sequenceFrames?: Array<{ name: string; previewUrl: string }>
|
||||
textureName: string
|
||||
textureFolder?: string
|
||||
anchorX?: number
|
||||
anchorY?: number
|
||||
weight: number
|
||||
locked: boolean
|
||||
}
|
||||
@@ -115,6 +169,43 @@ const displayPreviewUrl = computed(() => props.resource.imageMode === 'sequence'
|
||||
? props.resource.sequenceFrames?.[0]?.previewUrl || ''
|
||||
: props.resource.previewUrl)
|
||||
|
||||
const pivotOpen = ref(false)
|
||||
const pivotFullscreen = ref(false)
|
||||
const draftX = ref(0.5)
|
||||
const draftY = ref(0.5)
|
||||
const supportsPivot = computed(() => typeof props.resource.anchorX === 'number' && typeof props.resource.anchorY === 'number')
|
||||
const pivotCentered = computed(() =>
|
||||
Math.abs((props.resource.anchorX ?? 0.5) - 0.5) < 1e-4 && Math.abs((props.resource.anchorY ?? 0.5) - 0.5) < 1e-4,
|
||||
)
|
||||
const pivotSummary = computed(() => {
|
||||
const x = (Math.min(1, Math.max(0, props.resource.anchorX ?? 0.5)) * 100).toFixed(1)
|
||||
const y = (Math.min(1, Math.max(0, props.resource.anchorY ?? 0.5)) * 100).toFixed(1)
|
||||
if (pivotOpen.value) return `已开启:X ${x}% | Y ${y}%`
|
||||
if (pivotCentered.value) return '未开启,默认围绕图片正中心 (50%, 50%)'
|
||||
return `轴点已自定义:X ${x}% | Y ${y}%`
|
||||
})
|
||||
|
||||
function openPivotFullscreen() {
|
||||
draftX.value = props.resource.anchorX ?? 0.5
|
||||
draftY.value = props.resource.anchorY ?? 0.5
|
||||
pivotFullscreen.value = true
|
||||
}
|
||||
|
||||
function closePivotFullscreen(commit: boolean) {
|
||||
if (commit) {
|
||||
props.resource.anchorX = draftX.value
|
||||
props.resource.anchorY = draftY.value
|
||||
}
|
||||
pivotFullscreen.value = false
|
||||
}
|
||||
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape' && pivotFullscreen.value) closePivotFullscreen(false)
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('keydown', onKeydown))
|
||||
onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
|
||||
|
||||
const sequenceOrderText = computed(() => {
|
||||
const frames = props.resource.sequenceFrames || []
|
||||
if (!frames.length) return '未选择序列帧'
|
||||
@@ -156,4 +247,32 @@ function pickImage() {
|
||||
.weight-lock:hover:not(:disabled) { border-color: #7774ff; color: #b8b6ff; }
|
||||
.weight-lock.on { border-color: #7774ff; background: #514fd0; color: #fff; }
|
||||
.weight-lock:disabled { opacity: 0.35; cursor: not-allowed; }
|
||||
.pivot-toggle-row { display: flex; align-items: center; gap: 9px; margin-top: 9px; padding: 8px 9px; border: 1px solid #2c3a54; border-radius: 8px; background: #131b28; }
|
||||
.pivot-toggle-icon { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; flex: 0 0 32px; border: 1px solid #35435c; border-radius: 7px; background: #1c2638; color: #8f8cff; font-size: 17px; }
|
||||
.pivot-toggle-text { flex: 1; min-width: 0; }
|
||||
.pivot-toggle-title { color: #d6deee; font-size: 12px; font-weight: 600; }
|
||||
.pivot-toggle-sub { margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #7584a0; font-size: 9px; }
|
||||
.pivot-switch { position: relative; flex: 0 0 auto; cursor: pointer; }
|
||||
.pivot-switch input { position: absolute; opacity: 0; pointer-events: none; }
|
||||
.pivot-switch-ui { position: relative; display: block; width: 34px; height: 19px; border-radius: 10px; background: #354258; transition: background .16s ease; }
|
||||
.pivot-switch-ui::after { content: ''; position: absolute; left: 2px; top: 2px; width: 15px; height: 15px; border-radius: 50%; background: #fff; transition: transform .16s ease; }
|
||||
.pivot-switch input:checked + .pivot-switch-ui { background: #625ff1; }
|
||||
.pivot-switch input:checked + .pivot-switch-ui::after { transform: translateX(15px); }
|
||||
|
||||
.pivot-modal-backdrop { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(6, 9, 15, .78); backdrop-filter: blur(2px); }
|
||||
.pivot-modal { display: flex; flex-direction: column; width: min(980px, 94vw); max-height: 92vh; overflow: auto; padding: 14px; border: 1px solid #2f3f5c; border-radius: 14px; background: #121927; box-shadow: 0 24px 70px rgba(0, 0, 0, .6); }
|
||||
.pivot-modal-head { display: flex; align-items: flex-start; gap: 10px; padding-bottom: 10px; border-bottom: 1px solid #253148; }
|
||||
.pivot-modal-icon { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; flex: 0 0 34px; border: 1px solid #3b4a66; border-radius: 8px; background: #1c2638; color: #8f8cff; font-size: 18px; }
|
||||
.pivot-modal-info { flex: 1; min-width: 0; }
|
||||
.pivot-modal-title { display: flex; align-items: center; gap: 7px; color: #e4e9f5; font-size: 15px; font-weight: 700; }
|
||||
.pivot-modal-badge { padding: 2px 7px; border: 1px solid #4b4a8f; border-radius: 999px; background: #2c2b58; color: #b9b6ff; font-size: 10px; font-weight: 500; }
|
||||
.pivot-modal-desc { margin-top: 3px; color: #7f8daa; font-size: 11px; }
|
||||
.pivot-modal-close { width: 28px; height: 28px; padding: 0; border: 0; border-radius: 6px; background: transparent; color: #8b98b3; font-size: 15px; cursor: pointer; }
|
||||
.pivot-modal-close:hover { background: #253148; color: #fff; }
|
||||
.pivot-modal-hint { margin: 10px 0 0; color: #8b98b3; font-size: 11px; }
|
||||
.pivot-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; padding-top: 10px; border-top: 1px solid #253148; }
|
||||
.pivot-modal-cancel { height: 34px; padding: 0 16px; border: 1px solid #34435e; border-radius: 7px; background: #1a2334; color: #a9b6d0; font-size: 12px; cursor: pointer; }
|
||||
.pivot-modal-cancel:hover { border-color: #5d5aa8; color: #fff; }
|
||||
.pivot-modal-confirm { height: 34px; padding: 0 22px; border: 1px solid #7774ff; border-radius: 7px; background: #5b57e8; color: #fff; font-size: 12px; font-weight: 700; cursor: pointer; }
|
||||
.pivot-modal-confirm:hover { background: #6a66ff; }
|
||||
</style>
|
||||
Reference in new issue
Block a user