forked from goplus/builder
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
647 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 148 additions & 0 deletions
148
spx-gui/src/components/editor/sprite/animation/AnimationSettings.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
<template> | ||
<section class="wrapper"> | ||
<UIDropdown | ||
trigger="manual" | ||
:visible="activeSetting != null" | ||
placement="top" | ||
@click-outside="handleClickOutside" | ||
> | ||
<template #trigger> | ||
<ul class="settings"> | ||
<li | ||
class="setting" | ||
:class="{ active: activeSetting === 'duration' }" | ||
@click="handleSummaryClick('duration')" | ||
> | ||
<UIIcon type="timer" /> | ||
{{ $t({ en: 'Duration', zh: '时长' }) }} | ||
<span class="value">{{ formatDuration(animation.duration, 2) }}</span> | ||
</li> | ||
<li | ||
class="setting" | ||
:class="{ active: activeSetting === 'bound-state' }" | ||
@click="handleSummaryClick('bound-state')" | ||
> | ||
<UIIcon type="status" /> | ||
{{ $t({ en: 'Binding', zh: '绑定' }) }} | ||
<span v-if="boundStateNum > 0" class="value">{{ boundStateNum }}</span> | ||
</li> | ||
<li | ||
class="setting" | ||
:class="{ active: activeSetting === 'sound' }" | ||
@click="handleSummaryClick('sound')" | ||
> | ||
<UIIcon type="sound" /> | ||
{{ $t({ en: 'Sound', zh: '声音' }) }} | ||
<span class="value">{{ animation.sound }}</span> | ||
</li> | ||
</ul> | ||
</template> | ||
<DurationEditor | ||
v-show="activeSetting === 'duration'" | ||
:animation="animation" | ||
@close="handleEditorClose" | ||
/> | ||
<BoundStateEditor | ||
v-show="activeSetting === 'bound-state'" | ||
:animation="animation" | ||
:sprite="sprite" | ||
@close="handleEditorClose" | ||
/> | ||
<SoundEditor | ||
v-show="activeSetting === 'sound'" | ||
:animation="animation" | ||
@close="handleEditorClose" | ||
/> | ||
</UIDropdown> | ||
</section> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { computed, ref } from 'vue' | ||
import { formatDuration } from '@/utils/audio' | ||
import type { Sprite } from '@/models/sprite' | ||
import type { Animation } from '@/models/animation' | ||
import { UIDropdown, UIIcon, isInPopup } from '@/components/ui' | ||
import DurationEditor from './DurationEditor.vue' | ||
import BoundStateEditor from './state/BoundStateEditor.vue' | ||
import SoundEditor from './sound/SoundEditor.vue' | ||
const props = defineProps<{ | ||
sprite: Sprite | ||
animation: Animation | ||
}>() | ||
type Setting = 'duration' | 'bound-state' | 'sound' | ||
const activeSetting = ref<Setting | null>(null) | ||
function handleSummaryClick(setting: Setting) { | ||
activeSetting.value = activeSetting.value === setting ? null : setting | ||
} | ||
function handleEditorClose() { | ||
activeSetting.value = null | ||
} | ||
const boundStateNum = computed( | ||
() => props.sprite.getAnimationBoundStates(props.animation.name).length | ||
) | ||
function handleClickOutside(e: MouseEvent) { | ||
// There are popups (dropdown, modal, ...) in setting editor (e.g. "Record" in `SoundEditor`), we should not close the editor when user clicks in the popup content. | ||
// TODO: There should be a systematical solution for this, something like event propagation along the component tree instead of DOM tree. | ||
if (isInPopup(e.target as HTMLElement | null)) return | ||
activeSetting.value = null | ||
} | ||
</script> | ||
|
||
<style lang="scss" scoped> | ||
.wrapper { | ||
display: flex; | ||
justify-content: center; | ||
} | ||
.settings { | ||
display: flex; | ||
align-items: center; | ||
padding: 4px; | ||
gap: 4px; | ||
border-radius: var(--ui-border-radius-1); | ||
box-shadow: var(--ui-box-shadow-small); | ||
} | ||
.setting { | ||
display: flex; | ||
height: 32px; | ||
padding: 4px 12px; | ||
align-items: center; | ||
gap: 4px; | ||
border-radius: var(--ui-border-radius-1); | ||
font-size: 12px; | ||
line-height: 1.5; | ||
color: var(--ui-color-text-main); | ||
cursor: pointer; | ||
transition: 0.2s; | ||
&.active { | ||
color: var(--ui-color-primary-main); | ||
background: var(--ui-color-primary-200); | ||
} | ||
} | ||
.value { | ||
padding: 0px 5px; | ||
border-radius: 8px; | ||
max-width: 5em; | ||
overflow-x: hidden; | ||
white-space: nowrap; | ||
text-overflow: ellipsis; | ||
font-size: 10px; | ||
line-height: 1.6; | ||
color: var(--ui-color-grey-800); | ||
background-color: var(--ui-color-grey-400); | ||
} | ||
</style> |
34 changes: 34 additions & 0 deletions
34
spx-gui/src/components/editor/sprite/animation/DurationEditor.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<template> | ||
<UIDropdownModal | ||
:title="$t({ en: 'Adjust duration', zh: '调整时长' })" | ||
style="width: 280px" | ||
@cancel="emit('close')" | ||
@confirm="handleConfirm" | ||
> | ||
<UINumberInput v-model:value="duration" :min="0.01"> | ||
<template #prefix>{{ $t({ en: 'Duration', zh: '时长' }) }}:</template> | ||
<template #suffix>{{ $t({ en: 's', zh: '秒' }) }}</template> | ||
</UINumberInput> | ||
</UIDropdownModal> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { ref } from 'vue' | ||
import type { Animation } from '@/models/animation' | ||
import { UIDropdownModal, UINumberInput } from '@/components/ui' | ||
const props = defineProps<{ | ||
animation: Animation | ||
}>() | ||
const emit = defineEmits<{ | ||
close: [] | ||
}>() | ||
const duration = ref(props.animation.duration) | ||
function handleConfirm() { | ||
props.animation.setDuration(duration.value) | ||
emit('close') | ||
} | ||
</script> |
124 changes: 124 additions & 0 deletions
124
spx-gui/src/components/editor/sprite/animation/sound/SoundEditor.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<template> | ||
<UIDropdownModal | ||
:title="$t({ en: 'Select sound', zh: '选择声音' })" | ||
style="width: 320px; max-height: 400px" | ||
@cancel="emit('close')" | ||
@confirm="handleConfirm" | ||
> | ||
<ul class="sound-items"> | ||
<SoundItem | ||
v-for="sound in editorCtx.project.sounds" | ||
:key="sound.name" | ||
:sound="sound" | ||
:active="sound.name === selected" | ||
@click="handleSoundClick(sound.name)" | ||
/> | ||
<UIDropdown trigger="click" placement="top"> | ||
<template #trigger> | ||
<UIBlockItem class="add-sound"> | ||
<UIIcon class="icon" type="plus" /> | ||
</UIBlockItem> | ||
</template> | ||
<UIMenu> | ||
<UIMenuItem @click="handleAddFromLocalFile">{{ | ||
$t({ en: 'Select local file', zh: '选择本地文件' }) | ||
}}</UIMenuItem> | ||
<UIMenuItem @click="handleAddFromAssetLibrary">{{ | ||
$t({ en: 'Choose from asset library', zh: '从素材库选择' }) | ||
}}</UIMenuItem> | ||
<UIMenuItem @click="handleRecord">{{ $t({ en: 'Record', zh: '录音' }) }}</UIMenuItem> | ||
</UIMenu> | ||
</UIDropdown> | ||
</ul> | ||
<SoundRecorderModal v-model:visible="recorderVisible" @saved="handleRecorded" /> | ||
</UIDropdownModal> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { ref } from 'vue' | ||
import type { Animation } from '@/models/animation' | ||
import { | ||
UIDropdownModal, | ||
UIDropdown, | ||
UIMenu, | ||
UIMenuItem, | ||
UIBlockItem, | ||
UIIcon | ||
} from '@/components/ui' | ||
import { useEditorCtx } from '@/components/editor/EditorContextProvider.vue' | ||
import SoundItem from './SoundItem.vue' | ||
import { useAddAssetFromLibrary, useAddSoundFromLocalFile } from '@/components/asset' | ||
import SoundRecorderModal from '@/components/editor/sound/SoundRecorderModal.vue' | ||
import { useMessageHandle } from '@/utils/exception' | ||
import { AssetType } from '@/apis/asset' | ||
import type { Sound } from '@/models/sound' | ||
const props = defineProps<{ | ||
animation: Animation | ||
}>() | ||
const emit = defineEmits<{ | ||
close: [] | ||
}>() | ||
const editorCtx = useEditorCtx() | ||
const selected = ref(props.animation.sound) | ||
function handleSoundClick(sound: string) { | ||
selected.value = selected.value === sound ? null : sound | ||
} | ||
const addFromLocalFile = useAddSoundFromLocalFile(false) | ||
const handleAddFromLocalFile = useMessageHandle( | ||
async () => { | ||
const sound = await addFromLocalFile(editorCtx.project) | ||
selected.value = sound.name | ||
}, | ||
{ | ||
en: 'Failed to add sound from local file', | ||
zh: '从本地文件添加失败' | ||
} | ||
).fn | ||
const addAssetFromLibrary = useAddAssetFromLibrary(false) | ||
const handleAddFromAssetLibrary = useMessageHandle( | ||
async () => { | ||
const sounds = await addAssetFromLibrary(editorCtx.project, AssetType.Sound) | ||
selected.value = sounds[0].name | ||
}, | ||
{ en: 'Failed to add sound from asset library', zh: '从素材库添加失败' } | ||
).fn | ||
const recorderVisible = ref(false) | ||
function handleRecord() { | ||
recorderVisible.value = true | ||
} | ||
function handleRecorded(sound: Sound) { | ||
selected.value = sound.name | ||
} | ||
function handleConfirm() { | ||
props.animation.setSound(selected.value) | ||
emit('close') | ||
} | ||
</script> | ||
|
||
<style lang="scss" scoped> | ||
.sound-items { | ||
flex: 1 1 0; | ||
display: flex; | ||
flex-wrap: wrap; | ||
align-content: flex-start; | ||
gap: 12px; | ||
} | ||
.add-sound { | ||
justify-content: center; | ||
color: var(--ui-color-primary-main); | ||
.icon { | ||
width: 24px; | ||
height: 24px; | ||
} | ||
} | ||
</style> |
45 changes: 45 additions & 0 deletions
45
spx-gui/src/components/editor/sprite/animation/sound/SoundItem.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<template> | ||
<UIBlockItem :active="active"> | ||
<div class="content"> | ||
<SoundPlayer :src="audioSrc" color="primary" /> | ||
</div> | ||
<p class="name">{{ sound.name }}</p> | ||
</UIBlockItem> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { useFileUrl } from '@/utils/file' | ||
import { Sound } from '@/models/sound' | ||
import { UIBlockItem } from '@/components/ui' | ||
import SoundPlayer from '@/components/editor/sound/SoundPlayer.vue' | ||
const props = defineProps<{ | ||
sound: Sound | ||
active: boolean | ||
}>() | ||
const [audioSrc] = useFileUrl(() => props.sound.file) | ||
</script> | ||
|
||
<style lang="scss" scoped> | ||
.content { | ||
margin-top: 4px; | ||
width: 56px; | ||
height: 56px; | ||
padding: 10px; | ||
} | ||
.name { | ||
margin-top: 2px; | ||
font-size: 10px; | ||
line-height: 1.6; | ||
padding: 3px 8px 3px; | ||
width: 100%; | ||
overflow: hidden; | ||
white-space: nowrap; | ||
text-align: center; | ||
text-overflow: ellipsis; | ||
color: var(--ui-color-title); | ||
} | ||
</style> |
Oops, something went wrong.