diff --git a/src/components/CharacterButton.vue b/src/components/CharacterButton.vue index 2885f28383..8be19c2723 100644 --- a/src/components/CharacterButton.vue +++ b/src/components/CharacterButton.vue @@ -81,7 +81,8 @@ no-transition :ratio="1" :src=" - getDefaultStyle(characterInfo.metas.speakerUuid).iconPath + getDefaultStyleWrapper(characterInfo.metas.speakerUuid) + .iconPath " /> ), ); -const getDefaultStyle = (speakerUuid: SpeakerId) => { - // FIXME: 同一キャラが複数エンジンにまたがっているとき、順番が先のエンジンが必ず選択される - const characterInfo = props.characterInfos.find( - (info) => info.metas.speakerUuid === speakerUuid, +const getDefaultStyleWrapper = (speakerUuid: SpeakerId) => + getDefaultStyle( + speakerUuid, + props.characterInfos, + store.state.defaultStyleIds, ); - const defaultStyleId = store.state.defaultStyleIds.find( - (x) => x.speakerUuid === speakerUuid, - )?.defaultStyleId; - - const defaultStyle = - characterInfo?.metas.styles.find( - (style) => style.styleId === defaultStyleId, - ) ?? characterInfo?.metas.styles[0]; // デフォルトのスタイルIDが見つからない場合stylesの先頭を選択する - - if (defaultStyle == undefined) throw new Error("defaultStyle == undefined"); - - return defaultStyle; -}; const onSelectSpeaker = (speakerUuid: SpeakerId) => { - const style = getDefaultStyle(speakerUuid); + const style = getDefaultStyleWrapper(speakerUuid); emit("update:selectedVoice", { engineId: style.engineId, speakerId: speakerUuid, diff --git a/src/components/Talk/AudioCell.vue b/src/components/Talk/AudioCell.vue index c33fc4026d..a69fc56a6e 100644 --- a/src/components/Talk/AudioCell.vue +++ b/src/components/Talk/AudioCell.vue @@ -123,6 +123,7 @@ import { useShiftKey, useCommandOrControlKey, } from "@/composables/useModifierKey"; +import { getDefaultStyle } from "@/domain/talk"; const props = defineProps<{ audioKey: AudioKey; @@ -155,6 +156,10 @@ defineExpose({ removeCell: () => { removeCell(); }, + /** index番目のキャラクターを選ぶ */ + selectCharacterAt: (index: number) => { + selectCharacterAt(index); + }, }); const store = useStore(); @@ -490,6 +495,30 @@ const removeCell = async () => { } }; +// N番目のキャラクターを選ぶ +const selectCharacterAt = (index: number) => { + if (userOrderedCharacterInfos.value.length < index + 1) { + return; + } + const speakerUuid = userOrderedCharacterInfos.value[index].metas.speakerUuid; + const style = getDefaultStyle( + speakerUuid, + userOrderedCharacterInfos.value, + store.state.defaultStyleIds, + ); + const voice = { + engineId: style.engineId, + speakerId: speakerUuid, + styleId: style.styleId, + }; + store.dispatch("COMMAND_MULTI_CHANGE_VOICE", { + audioKeys: isMultiSelectEnabled.value + ? store.getters.SELECTED_AUDIO_KEYS + : [props.audioKey], + voice, + }); +}; + // 削除ボタンの有効/無効判定 const enableDeleteButton = computed(() => { return ( diff --git a/src/components/Talk/TalkEditor.vue b/src/components/Talk/TalkEditor.vue index 0e1064fd69..cb11bd6e5b 100644 --- a/src/components/Talk/TalkEditor.vue +++ b/src/components/Talk/TalkEditor.vue @@ -142,6 +142,8 @@ import { PresetKey, SplitterPositionType, Voice, + HotkeyActionNameType, + actionPostfixSelectNthCharacter, } from "@/type/preload"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; import onetimeWatch from "@/helpers/onetimeWatch"; @@ -258,12 +260,29 @@ registerHotkeyWithCleanup({ } }, }); +for (let i = 0; i < 10; i++) { + registerHotkeyWithCleanup({ + editor: "talk", + enableInTextbox: true, + name: `${i + 1}${actionPostfixSelectNthCharacter}` as HotkeyActionNameType, + callback: () => { + if (!uiLocked.value) { + onCharacterSelectHotkey(i); + } + }, + }); +} const removeAudioItem = async () => { if (activeAudioKey.value == undefined) throw new Error(); audioCellRefs[activeAudioKey.value].removeCell(); }; +const onCharacterSelectHotkey = async (selectedCharacterIndex: number) => { + if (activeAudioKey.value == undefined) throw new Error(); + audioCellRefs[activeAudioKey.value].selectCharacterAt(selectedCharacterIndex); +}; + // view const DEFAULT_PORTRAIT_PANE_WIDTH = 22; // % const MIN_PORTRAIT_PANE_WIDTH = 0; diff --git a/src/domain/talk.ts b/src/domain/talk.ts new file mode 100644 index 0000000000..f49575dbe9 --- /dev/null +++ b/src/domain/talk.ts @@ -0,0 +1,25 @@ +import { CharacterInfo, DefaultStyleId, SpeakerId } from "@/type/preload"; + +/** 話者に対応するデフォルトスタイルを取得する */ +export const getDefaultStyle = ( + speakerUuid: SpeakerId, + characterInfos: CharacterInfo[], + defaultStyleIds: DefaultStyleId[], +) => { + // FIXME: 同一キャラが複数エンジンにまたがっているとき、順番が先のエンジンが必ず選択される + const characterInfo = characterInfos.find( + (info) => info.metas.speakerUuid === speakerUuid, + ); + const defaultStyleId = defaultStyleIds.find( + (x) => x.speakerUuid === speakerUuid, + )?.defaultStyleId; + + const defaultStyle = + characterInfo?.metas.styles.find( + (style) => style.styleId === defaultStyleId, + ) ?? characterInfo?.metas.styles[0]; // デフォルトのスタイルIDが見つからない場合stylesの先頭を選択する + + if (defaultStyle == undefined) throw new Error("defaultStyle == undefined"); + + return defaultStyle; +}; diff --git a/src/type/preload.ts b/src/type/preload.ts index ba0c283c91..87c005da68 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -62,6 +62,9 @@ export type VoiceId = z.infer; export const VoiceId = (voice: Voice): VoiceId => voiceIdSchema.parse(`${voice.engineId}:${voice.speakerId}:${voice.styleId}`); +// 共通のアクション名 +export const actionPostfixSelectNthCharacter = "番目のキャラクターを選択"; + // ホットキーを追加したときは設定のマイグレーションが必要 export const defaultHotkeySettings: HotkeySettingType[] = [ { @@ -176,6 +179,14 @@ export const defaultHotkeySettings: HotkeySettingType[] = [ action: "全セルを選択", combination: HotkeyCombination(!isMac ? "Ctrl Shift A" : "Meta Shift A"), }, + ...Array.from({ length: 10 }, (_, index) => { + const roleKey = index == 9 ? 0 : index + 1; + return { + action: + `${index + 1}${actionPostfixSelectNthCharacter}` as HotkeyActionNameType, + combination: HotkeyCombination((!isMac ? "Ctrl " : "Meta ") + roleKey), + }; + }), ]; export const defaultToolbarButtonSetting: ToolbarSettingType = [ @@ -463,6 +474,16 @@ export const hotkeyActionNameSchema = z.enum([ "すべて選択", "選択解除", "全セルを選択", + `1${actionPostfixSelectNthCharacter}`, + `2${actionPostfixSelectNthCharacter}`, + `3${actionPostfixSelectNthCharacter}`, + `4${actionPostfixSelectNthCharacter}`, + `5${actionPostfixSelectNthCharacter}`, + `6${actionPostfixSelectNthCharacter}`, + `7${actionPostfixSelectNthCharacter}`, + `8${actionPostfixSelectNthCharacter}`, + `9${actionPostfixSelectNthCharacter}`, + `10${actionPostfixSelectNthCharacter}`, ]); export type HotkeyActionNameType = z.infer;