@@ -51,7 +56,16 @@ const settings = useSettings()
Primary color
-
+
@@ -124,6 +138,26 @@ const settings = useSettings()
500
+
+
+
+
+
diff --git a/packages/stage-ui/src/components/Live2D/Model.vue b/packages/stage-ui/src/components/Live2D/Model.vue
index 3a6d0bb09..e56bbdfc3 100644
--- a/packages/stage-ui/src/components/Live2D/Model.vue
+++ b/packages/stage-ui/src/components/Live2D/Model.vue
@@ -38,6 +38,12 @@ const dark = useDark()
const breakpoints = useBreakpoints(breakpointsTailwind)
const isMobile = computed(() => breakpoints.between('sm', 'md').value || breakpoints.smaller('sm').value)
const idleEyeFocus = useLive2DIdleEyeFocus()
+const dropShadowFilter = ref(new DropShadowFilter({
+ alpha: 0.2,
+ blur: 0,
+ distance: 20,
+ rotation: 45,
+}))
function getCoreModel() {
return model.value!.internalModel.coreModel as any
@@ -67,6 +73,7 @@ const {
live2dLoadSource,
live2dModelUrl,
themeColorsHue,
+ themeColorsHueDynamic,
} = storeToRefs(useSettings())
const currentMotion = ref<{ group: string, index: number }>({ group: 'Idle', index: 0 })
@@ -199,24 +206,36 @@ const handleResize = useDebounceFn(() => {
}, 100)
const dropShadowColorComputer = ref()
+const dropShadowAnimationId = ref(0)
function updateDropShadowFilter() {
if (model.value) {
const color = getComputedStyle(dropShadowColorComputer.value!).backgroundColor
- const colorNumber = Number(formatHex(color)!.replace('#', '0x'))
- model.value.filters = [new DropShadowFilter({
- color: colorNumber,
- alpha: 0.2,
- blur: 0,
- distance: 20,
- rotation: 45,
- })]
+ dropShadowFilter.value.color = Number(formatHex(color)!.replace('#', '0x'))
+ model.value.filters = [dropShadowFilter.value]
}
}
watch([() => props.width, () => props.height], () => handleResize())
watch(dark, updateDropShadowFilter, { immediate: true })
watch([model, themeColorsHue], updateDropShadowFilter)
+
+// TODO: This is hacky!
+function updateDropShadowFilterLoop() {
+ updateDropShadowFilter()
+ dropShadowAnimationId.value = requestAnimationFrame(updateDropShadowFilterLoop)
+}
+
+watch(themeColorsHueDynamic, () => {
+ if (themeColorsHueDynamic.value) {
+ dropShadowAnimationId.value = requestAnimationFrame(updateDropShadowFilterLoop)
+ }
+ else {
+ cancelAnimationFrame(dropShadowAnimationId.value)
+ dropShadowAnimationId.value = 0
+ }
+}, { immediate: true })
+
watch(mouthOpenSize, value => getCoreModel().setParameterValueById('ParamMouthOpenY', value))
watch(pixiApp, initLive2DPixiStage)
watch(live2dCurrentMotion, value => setMotion(value.group, value.index))
@@ -233,6 +252,7 @@ watchDebounced(loadingLive2dModel, (value) => {
onMounted(updateDropShadowFilter)
onUnmounted(() => {
+ cancelAnimationFrame(dropShadowAnimationId.value)
model.value && pixiApp.value?.stage.removeChild(model.value)
})
diff --git a/packages/stage-ui/src/stores/settings.ts b/packages/stage-ui/src/stores/settings.ts
index 6212be74c..b8cc47021 100644
--- a/packages/stage-ui/src/stores/settings.ts
+++ b/packages/stage-ui/src/stores/settings.ts
@@ -4,6 +4,8 @@ import { computed, onMounted, ref, watch } from 'vue'
import { Voice } from '../constants/elevenlabs'
+export const DEFAULT_THEME_COLORS_HUE = 354.31
+
export const useSettings = defineStore('settings', () => {
const selectedAudioDevice = ref()
@@ -35,7 +37,8 @@ export const useSettings = defineStore('settings', () => {
const disableTransitions = useLocalStorage('settings/disable-transitions', false)
- const themeColorsHue = useLocalStorage('settings/theme/colors/hue', 354.31)
+ const themeColorsHue = useLocalStorage('settings/theme/colors/hue', DEFAULT_THEME_COLORS_HUE)
+ const themeColorsHueDynamic = useLocalStorage('settings/theme/colors/hue-dynamic', false)
watch(isAudioInputOn, (value) => {
if (value === 'false') {
@@ -75,6 +78,7 @@ export const useSettings = defineStore('settings', () => {
language,
stageView,
themeColorsHue,
+ themeColorsHueDynamic,
isAudioInputOn,
selectedAudioDevice,
selectedAudioDeviceId,