style: use screen component for scenes
This commit is contained in:
Vendored
+2
@@ -288,6 +288,7 @@ declare global {
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWhisper: typeof import('./composables/whisper')['useWhisper']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
@@ -596,6 +597,7 @@ declare module 'vue' {
|
||||
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
||||
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
||||
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
|
||||
readonly useWhisper: UnwrapRef<typeof import('./composables/whisper')['useWhisper']>
|
||||
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
|
||||
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
|
||||
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { EmotionAngryMotionName, EmotionAwkwardMotionName, EmotionHappyMotionName, EmotionQuestionMotionName, EmotionSadMotionName, EmotionSurpriseMotionName, EmotionThinkMotionName } from '~/constants/emotions'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
model: string
|
||||
mouthOpenSize?: number
|
||||
}>(), {
|
||||
mouthOpenSize: 0,
|
||||
})
|
||||
|
||||
const modelRef = ref<{
|
||||
setMotion: (motionName: string) => Promise<void>
|
||||
}>()
|
||||
|
||||
defineExpose({
|
||||
setMotion: async (motionName: string): Promise<void> => {
|
||||
await modelRef.value?.setMotion(motionName)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Screen v-slot="{ canvasHeight, canvasWidth }" relative>
|
||||
<div z="10" top="2" absolute w-full flex="~ col" gap-2>
|
||||
<div flex="~ row" w-full flex-wrap gap-2>
|
||||
<button
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setMotion(EmotionSurpriseMotionName)"
|
||||
>
|
||||
🤯 Surprised
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setMotion(EmotionSadMotionName)"
|
||||
>
|
||||
😫 Sad
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setMotion(EmotionAngryMotionName)"
|
||||
>
|
||||
😠 Angry
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setMotion(EmotionHappyMotionName)"
|
||||
>
|
||||
😄 Happy
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setMotion(EmotionAwkwardMotionName)"
|
||||
>
|
||||
😳 Awkward
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setMotion(EmotionQuestionMotionName)"
|
||||
>
|
||||
🤔 Question
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setMotion(EmotionThinkMotionName)"
|
||||
>
|
||||
🤨 Think
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Live2DViewer ref="modelRef" :canvas-width="canvasWidth" :canvas-height="canvasHeight" :model="model" :mouth-open-size="mouthOpenSize" />
|
||||
</Screen>
|
||||
</template>
|
||||
@@ -3,7 +3,7 @@ import type { InternalModel } from 'pixi-live2d-display/cubism4'
|
||||
import { Application } from '@pixi/app'
|
||||
import { extensions } from '@pixi/extensions'
|
||||
import { Ticker, TickerPlugin } from '@pixi/ticker'
|
||||
import { breakpointsTailwind, useBreakpoints, useElementBounding, useWindowSize } from '@vueuse/core'
|
||||
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
|
||||
import { Live2DModel, MotionPreloadStrategy, MotionPriority } from 'pixi-live2d-display/cubism4'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
@@ -23,41 +23,8 @@ const mouthOpenSize = computed(() => {
|
||||
})
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
const { width, height } = useWindowSize()
|
||||
const containerElementBounding = useElementBounding(containerRef, { immediate: true, windowResize: true, reset: true })
|
||||
|
||||
const isMobile = computed(() => breakpoints.between('sm', 'md').value || breakpoints.smaller('sm').value)
|
||||
const isTablet = computed(() => breakpoints.between('md', 'lg').value)
|
||||
const isDesktop = computed(() => breakpoints.greaterOrEqual('lg').value)
|
||||
|
||||
const canvasWidth = computed(() => {
|
||||
if (isDesktop.value)
|
||||
return containerElementBounding.width.value
|
||||
else if (isMobile.value)
|
||||
return (width.value - 16) // padding
|
||||
else if (isTablet.value)
|
||||
return (width.value - 16) // padding
|
||||
else
|
||||
return containerElementBounding.width.value
|
||||
})
|
||||
|
||||
const canvasHeight = ref(0)
|
||||
watch([width, height, containerRef], () => {
|
||||
const bounding = containerRef.value?.getBoundingClientRect()
|
||||
|
||||
if (isDesktop.value) {
|
||||
canvasHeight.value = bounding?.height || 0
|
||||
}
|
||||
else if (isMobile.value) {
|
||||
canvasHeight.value = bounding?.height || 0
|
||||
}
|
||||
else if (isTablet.value) {
|
||||
canvasHeight.value = bounding?.height || 0
|
||||
}
|
||||
else {
|
||||
canvasHeight.value = 600
|
||||
}
|
||||
})
|
||||
const { height, width } = useElementBounding(containerRef, { immediate: true, windowResize: true, reset: true })
|
||||
|
||||
function getCoreModel() {
|
||||
return model.value!.internalModel.coreModel as any
|
||||
@@ -72,8 +39,8 @@ function setScale(model: Ref<Live2DModel<InternalModel> | undefined>) {
|
||||
offsetFactor = 2.5
|
||||
}
|
||||
|
||||
const heightScale = canvasHeight.value * 0.95 / model.value.height * offsetFactor
|
||||
const widthScale = canvasWidth.value * 0.95 / model.value.width * offsetFactor
|
||||
const heightScale = height.value * 0.95 / model.value.height * offsetFactor
|
||||
const widthScale = width.value * 0.95 / model.value.width * offsetFactor
|
||||
const scale = Math.min(heightScale, widthScale)
|
||||
|
||||
model.value.scale.set(scale, scale)
|
||||
@@ -85,8 +52,8 @@ async function initLive2DPixiStage(parent: HTMLDivElement) {
|
||||
extensions.add(TickerPlugin)
|
||||
|
||||
pixiApp.value = new Application({
|
||||
width: canvasWidth.value,
|
||||
height: canvasHeight.value,
|
||||
width: width.value,
|
||||
height: height.value,
|
||||
backgroundAlpha: 0,
|
||||
})
|
||||
|
||||
@@ -97,8 +64,8 @@ async function initLive2DPixiStage(parent: HTMLDivElement) {
|
||||
model.value = await Live2DModel.from(props.model, { motionPreload: MotionPreloadStrategy.ALL })
|
||||
pixiApp.value.stage.addChild(model.value as any)
|
||||
|
||||
model.value.x = canvasWidth.value / 2
|
||||
model.value.y = canvasHeight.value
|
||||
model.value.x = width.value / 2
|
||||
model.value.y = height.value
|
||||
model.value.rotation = Math.PI
|
||||
model.value.skew.x = Math.PI
|
||||
model.value.anchor.set(0.5, 0.5)
|
||||
@@ -117,18 +84,18 @@ async function setMotion(motionName: string) {
|
||||
await model.value!.motion(motionName, undefined, MotionPriority.FORCE)
|
||||
}
|
||||
|
||||
watch([width, height, canvasWidth, canvasHeight], () => {
|
||||
watch([width, height, width, height], () => {
|
||||
if (pixiApp.value)
|
||||
pixiApp.value.renderer.resize(canvasWidth.value, canvasHeight.value)
|
||||
pixiApp.value.renderer.resize(width.value, height.value)
|
||||
|
||||
if (pixiAppCanvas.value) {
|
||||
pixiAppCanvas.value.width = canvasWidth.value
|
||||
pixiAppCanvas.value.height = canvasHeight.value
|
||||
pixiAppCanvas.value.width = width.value
|
||||
pixiAppCanvas.value.height = height.value
|
||||
}
|
||||
|
||||
if (model.value) {
|
||||
model.value.x = canvasWidth.value / 2
|
||||
model.value.y = canvasHeight.value
|
||||
model.value.x = width.value / 2
|
||||
model.value.y = height.value
|
||||
}
|
||||
})
|
||||
|
||||
@@ -136,8 +103,6 @@ onMounted(async () => {
|
||||
if (!containerRef.value)
|
||||
return
|
||||
|
||||
containerElementBounding.update()
|
||||
|
||||
await initLive2DPixiStage(containerRef.value)
|
||||
})
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useSettings } from '../stores/settings'
|
||||
import { encodeWAVToBase64 } from '../utils/binary'
|
||||
import { asyncIteratorFromReadableStream } from '../utils/iterator'
|
||||
import BasicTextarea from './BasicTextarea.vue'
|
||||
import Live2DViewer from './Live2DViewer.vue'
|
||||
import Live2DScene from './Live2DScene.vue'
|
||||
import Settings from './Settings.vue'
|
||||
import ThreeDScene from './ThreeDScene.vue'
|
||||
|
||||
@@ -304,7 +304,7 @@ onUnmounted(() => {
|
||||
<Settings />
|
||||
</div>
|
||||
<div flex="~ row 1" max-h="[calc(100vh-220px)] <sm:[calc(100vh-320px)]" relative h-full w-full items-end gap-2>
|
||||
<Live2DViewer
|
||||
<Live2DScene
|
||||
v-if="stageView === '2d'"
|
||||
ref="live2DViewerRef"
|
||||
:mouth-open-size="mouthOpenSize"
|
||||
|
||||
@@ -112,33 +112,33 @@ defineExpose({
|
||||
</div>
|
||||
</Collapsable>
|
||||
</div>
|
||||
<div flex="~ row" w-full gap-2>
|
||||
<div flex="~ row" w-full flex-wrap gap-2>
|
||||
<button
|
||||
rounded-lg bg="zinc-200 dark:zinc-800/50" px-2 py-1
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setExpression('neutral')"
|
||||
>
|
||||
🙂 Neutral
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-200 dark:zinc-800/50" px-2 py-1
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setExpression('surprised')"
|
||||
>
|
||||
🤯 Surprised
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-200 dark:zinc-800/50" px-2 py-1
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setExpression('sad')"
|
||||
>
|
||||
😫 Sad
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-200 dark:zinc-800/50" px-2 py-1
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setExpression('angry')"
|
||||
>
|
||||
😠 Angry
|
||||
</button>
|
||||
<button
|
||||
rounded-lg bg="zinc-200 dark:zinc-800/50" px-2 py-1
|
||||
rounded-lg bg="zinc-100 dark:zinc-800/50" px-2 py-1
|
||||
@click="modelRef?.setExpression('happy')"
|
||||
>
|
||||
😄 Happy
|
||||
|
||||
Vendored
-7
@@ -18,12 +18,5 @@ declare module 'vue-router/auto-routes' {
|
||||
* Route name map generated by unplugin-vue-router
|
||||
*/
|
||||
export interface RouteNamedMap {
|
||||
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||
'/audio': RouteRecordInfo<'/audio', '/audio', Record<never, never>, Record<never, never>>,
|
||||
'/queue': RouteRecordInfo<'/queue', '/queue', Record<never, never>, Record<never, never>>,
|
||||
'/test/filter-message': RouteRecordInfo<'/test/filter-message', '/test/filter-message', Record<never, never>, Record<never, never>>,
|
||||
'/test/queues/delays': RouteRecordInfo<'/test/queues/delays', '/test/queues/delays', Record<never, never>, Record<never, never>>,
|
||||
'/test/queues/emotions': RouteRecordInfo<'/test/queues/emotions', '/test/queues/emotions', Record<never, never>, Record<never, never>>,
|
||||
'/test/queues/messages': RouteRecordInfo<'/test/queues/messages', '/test/queues/messages', Record<never, never>, Record<never, never>>,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user