Skip to content

Commit

Permalink
スタイルのないキャラのためのUI・機能修正 (#510)
Browse files Browse the repository at this point in the history
* refactor "is unset default style id"
make it possible to get by uuid of each speaker

* fix miss(logical operation)

* only show added new character in default style select dialog at startup

* fix portrait in default style select dialog

* remove separator and arrow right icon from character info that has not style

* fix menu bar

* add fix me comment

* add comment and refactor

* add comment close dialog

* use throw Error
  • Loading branch information
y-chan committed Nov 26, 2021
1 parent b3f1624 commit 6b96323
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 103 deletions.
5 changes: 3 additions & 2 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,8 +682,9 @@ ipcMainHandle("CHANGE_PIN_WINDOW", () => {
}
});

ipcMainHandle("IS_UNSET_DEFAULT_STYLE_IDS", () => {
return store.get("defaultStyleIds").length === 0;
ipcMainHandle("IS_UNSET_DEFAULT_STYLE_ID", (_, speakerUuid) => {
const defaultStyleIds = store.get("defaultStyleIds");
return !defaultStyleIds.find((style) => style.speakerUuid === speakerUuid);
});

ipcMainHandle("GET_DEFAULT_STYLE_IDS", () => {
Expand Down
108 changes: 59 additions & 49 deletions src/components/AudioCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,57 +53,67 @@
<div>{{ characterInfo.metas.speakerName }}</div>
</q-btn>

<q-separator vertical />

<div
class="flex items-center q-px-sm q-py-none cursor-pointer"
:class="
subMenuOpenFlags[characterIndex] && 'opened-character-item'
"
@mouseover="reassignSubMenuOpen(characterIndex)"
@mouseleave="reassignSubMenuOpen.cancel()"
>
<q-icon name="keyboard_arrow_right" color="grey-6" size="sm" />

<q-menu
no-parent-event
anchor="top end"
self="top start"
transition-show="none"
transition-hide="none"
class="character-menu"
v-model="subMenuOpenFlags[characterIndex]"
<!-- スタイルが2つ以上あるものだけ、スタイル選択ボタンを表示する-->
<template v-if="characterInfo.metas.styles.length >= 2">
<q-separator vertical />

<div
class="flex items-center q-px-sm q-py-none cursor-pointer"
:class="
subMenuOpenFlags[characterIndex] && 'opened-character-item'
"
@mouseover="reassignSubMenuOpen(characterIndex)"
@mouseleave="reassignSubMenuOpen.cancel()"
>
<q-list>
<q-item
v-for="(style, styleIndex) in characterInfo.metas.styles"
:key="styleIndex"
clickable
v-close-popup
active-class="selected-character-item"
:active="style.styleId === selectedStyle.styleId"
@click="changeStyleId(style.styleId)"
>
<q-avatar rounded size="2rem" class="q-mr-md">
<q-img
no-spinner
no-transition
:ratio="1"
:src="characterInfo.metas.styles[styleIndex].iconPath"
/>
</q-avatar>
<q-item-section v-if="style.styleName"
>{{ characterInfo.metas.speakerName }} ({{
style.styleName
}})</q-item-section
<q-icon
name="keyboard_arrow_right"
color="grey-6"
size="sm"
/>

<q-menu
no-parent-event
anchor="top end"
self="top start"
transition-show="none"
transition-hide="none"
class="character-menu"
v-model="subMenuOpenFlags[characterIndex]"
>
<q-list>
<q-item
v-for="(style, styleIndex) in characterInfo.metas
.styles"
:key="styleIndex"
clickable
v-close-popup
active-class="selected-character-item"
:active="style.styleId === selectedStyle.styleId"
@click="changeStyleId(style.styleId)"
>
<q-item-section v-else>{{
characterInfo.metas.speakerName
}}</q-item-section>
</q-item>
</q-list>
</q-menu>
</div>
<q-avatar rounded size="2rem" class="q-mr-md">
<q-img
no-spinner
no-transition
:ratio="1"
:src="
characterInfo.metas.styles[styleIndex].iconPath
"
/>
</q-avatar>
<q-item-section v-if="style.styleName"
>{{ characterInfo.metas.speakerName }} ({{
style.styleName
}})</q-item-section
>
<q-item-section v-else>{{
characterInfo.metas.speakerName
}}</q-item-section>
</q-item>
</q-list>
</q-menu>
</div>
</template>
</q-btn-group>
</q-item>
</q-list>
Expand Down
129 changes: 89 additions & 40 deletions src/components/DefaultStyleSelectDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,26 @@
<q-header class="q-py-sm">
<q-toolbar>
<div class="column">
<q-toolbar-title v-if="isFirstTime" class="text-display text-h6"
>「{{
characterInfos[pageIndex].metas.speakerName
}}」のデフォルトのスタイル(喋り方)を選んでください</q-toolbar-title
>
<q-toolbar-title
v-if="isFirstTime && showCharacterInfos.length > 0"
class="text-display text-h6"
>「{{ showCharacterInfos[pageIndex].metas.speakerName }}」の{{
showCharacterInfos[pageIndex].metas.styles.length > 1
? "デフォルトのスタイル(喋り方)を選んでください"
: "サンプル音声を視聴できます"
}}
</q-toolbar-title>
<q-toolbar-title v-else class="text-display"
>設定 / デフォルトスタイル</q-toolbar-title
>設定 / デフォルトスタイル・試聴</q-toolbar-title
>
<span
v-if="
isFirstTime &&
showCharacterInfos.length > 0 &&
showCharacterInfos[pageIndex].metas.styles.length > 1
"
class="text-display text-caption q-ml-sm"
>
<span v-if="isFirstTime" class="text-display text-caption q-ml-sm">
※後からでも変更できます
</span>
</div>
Expand All @@ -38,11 +49,11 @@
/>

<div class="text-subtitle2 text-no-wrap text-display q-mr-md">
{{ pageIndex + 1 }} / {{ characterInfos.length }}
{{ pageIndex + 1 }} / {{ showCharacterInfos.length }}
</div>

<q-btn
v-if="pageIndex + 1 < characterInfos.length"
v-if="pageIndex + 1 < showCharacterInfos.length"
unelevated
label="次へ"
color="background-light"
Expand Down Expand Up @@ -74,17 +85,18 @@
>
<div class="character-portrait-wrapper">
<img
:src="characterInfos[pageIndex].portraitPath"
v-if="showCharacterInfos.length > 0"
:src="showCharacterInfos[pageIndex].portraitPath"
class="character-portrait"
/>
</div>
</q-drawer>

<q-page-container>
<q-page v-if="characterInfos && selectedStyleIndexes">
<q-page v-if="showCharacterInfos && selectedStyleIndexes">
<q-tab-panels v-model="pageIndex">
<q-tab-panel
v-for="(characterInfo, characterIndex) of characterInfos"
v-for="(characterInfo, characterIndex) of showCharacterInfos"
:key="characterIndex"
:name="characterIndex"
>
Expand Down Expand Up @@ -163,7 +175,7 @@
<script lang="ts">
import { defineComponent, computed, ref, PropType, watch } from "vue";
import { useStore } from "@/store";
import { CharacterInfo, StyleInfo } from "@/type/preload";
import { CharacterInfo, DefaultStyleId, StyleInfo } from "@/type/preload";
export default defineComponent({
name: "DefaultStyleSelectDialog",
Expand All @@ -187,6 +199,11 @@ export default defineComponent({
set: (val) => emit("update:modelValue", val),
});
// アップデートで増えたキャラ・スタイルがあれば、それらに対して起動時にデフォルトスタイル選択・試聴を問うための変数
// その他の場合は、characterInfosと同じになる
// FIXME: 現状はスタイルが増えてもデフォルトスタイルを問えないので、そこを改修しなければならない
const showCharacterInfos = ref(props.characterInfos);
const isFirstTime = ref(false);
const selectedStyleIndexes = ref<(number | undefined)[]>([]);
Expand All @@ -195,24 +212,43 @@ export default defineComponent({
() => props.modelValue,
async (newValue, oldValue) => {
if (!oldValue && newValue) {
const isUnsetDefaultStyleIds = await store.dispatch(
"IS_UNSET_DEFAULT_STYLE_IDS"
showCharacterInfos.value = [];
selectedStyleIndexes.value = await Promise.all(
props.characterInfos.map(async (info) => {
const styles = info.metas.styles;
const isUnsetDefaultStyleId = await store.dispatch(
"IS_UNSET_DEFAULT_STYLE_ID",
{ speakerUuid: info.metas.speakerUuid }
);
if (isUnsetDefaultStyleId) {
isFirstTime.value = true;
showCharacterInfos.value.push(info);
return undefined;
}
const defaultStyleId = store.state.defaultStyleIds.find(
(x) => x.speakerUuid === info.metas.speakerUuid
)?.defaultStyleId;
const index = styles.findIndex(
(style) => style.styleId === defaultStyleId
);
return index === -1 ? undefined : index;
})
);
isFirstTime.value = isUnsetDefaultStyleIds;
selectedStyleIndexes.value = props.characterInfos.map((info) => {
// FIXME: キャラクターごとにデフォルスタイル選択済みか保存できるようになるべき
if (isFirstTime.value) return undefined;
const defaultStyleId = store.state.defaultStyleIds.find(
(x) => x.speakerUuid === info.metas.speakerUuid
)?.defaultStyleId;
const index = info.metas.styles.findIndex(
(style) => style.styleId === defaultStyleId
if (!isFirstTime.value) {
showCharacterInfos.value = props.characterInfos;
} else {
selectedStyleIndexes.value = showCharacterInfos.value.map(
(info) => {
if (info.metas.styles.length > 1) {
return undefined;
} else {
return info.metas.styles[0].styleId;
}
}
);
return index === -1 ? undefined : index;
});
}
}
}
);
Expand All @@ -222,7 +258,7 @@ export default defineComponent({
// 音声を再生する。同じstyleIndexだったら停止する。
const selectedStyleInfo =
props.characterInfos[characterIndex].metas.styles[styleIndex];
showCharacterInfos.value[characterIndex].metas.styles[styleIndex];
if (
playing.value !== undefined &&
playing.value.styleId === selectedStyleInfo.styleId
Expand Down Expand Up @@ -272,13 +308,28 @@ export default defineComponent({
pageIndex.value++;
};
// 既に設定が存在する場合があるので、新しい設定と既存設定を合成させる
const closeDialog = () => {
const defaultStyleIds = props.characterInfos.map((info, idx) => ({
speakerUuid: info.metas.speakerUuid,
defaultStyleId:
info.metas.styles[selectedStyleIndexes.value[idx] ?? 0].styleId,
}));
const defaultStyleIds = JSON.parse(
JSON.stringify(store.state.defaultStyleIds)
) as DefaultStyleId[];
showCharacterInfos.value.forEach((info, idx) => {
const defaultStyleInfo = {
speakerUuid: info.metas.speakerUuid,
defaultStyleId:
info.metas.styles[selectedStyleIndexes.value[idx] ?? 0].styleId,
};
const nowSettingIndex = defaultStyleIds.findIndex(
(s) => s.speakerUuid === info.metas.speakerUuid
);
if (nowSettingIndex !== -1) {
defaultStyleIds[nowSettingIndex] = defaultStyleInfo;
} else {
defaultStyleIds.push(defaultStyleInfo);
}
});
store.dispatch("SET_DEFAULT_STYLE_IDS", defaultStyleIds);
isFirstTime.value = false;
stop();
modelValueComputed.value = false;
Expand All @@ -287,6 +338,7 @@ export default defineComponent({
return {
modelValueComputed,
showCharacterInfos,
isFirstTime,
selectedStyleIndexes,
selectStyleIndex,
Expand All @@ -312,15 +364,12 @@ export default defineComponent({
}
.character-portrait-wrapper {
display: grid;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow: hidden;
.character-portrait {
object-fit: none;
object-position: center top;
width: 100%;
height: fit-content;
margin: auto;
}
}
.q-tab-panels {
Expand Down
2 changes: 1 addition & 1 deletion src/components/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ export default defineComponent({
},
{
type: "button",
label: "デフォルトスタイル",
label: "デフォルトスタイル・試聴",
onClick() {
store.dispatch("IS_DEFAULT_STYLE_SELECT_DIALOG_OPEN", {
isDefaultStyleSelectDialogOpen: true,
Expand Down
4 changes: 2 additions & 2 deletions src/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ const api: Sandbox = {
return ipcRenderer.invoke("HOTKEY_SETTINGS", { newData });
},

isUnsetDefaultStyleIds: async () => {
return await ipcRendererInvoke("IS_UNSET_DEFAULT_STYLE_IDS");
isUnsetDefaultStyleId: async (speakerUuid: string) => {
return await ipcRendererInvoke("IS_UNSET_DEFAULT_STYLE_ID", speakerUuid);
},

getDefaultStyleIds: async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ export const indexStore: VoiceVoxStoreOptions<
LOG_INFO(_, ...params: unknown[]) {
window.electron.logInfo(...params);
},
async IS_UNSET_DEFAULT_STYLE_IDS() {
return await window.electron.isUnsetDefaultStyleIds();
async IS_UNSET_DEFAULT_STYLE_ID(_, { speakerUuid }) {
return await window.electron.isUnsetDefaultStyleId(speakerUuid);
},
async LOAD_DEFAULT_STYLE_IDS({ commit }) {
const defaultStyleIds = await window.electron.getDefaultStyleIds();
Expand Down
4 changes: 2 additions & 2 deletions src/store/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,8 @@ type IndexStoreTypes = {
action(): Promise<string>;
};

IS_UNSET_DEFAULT_STYLE_IDS: {
action(): Promise<boolean>;
IS_UNSET_DEFAULT_STYLE_ID: {
action(payload: { speakerUuid: string }): Promise<boolean>;
};

LOAD_DEFAULT_STYLE_IDS: {
Expand Down

0 comments on commit 6b96323

Please sign in to comment.