diff --git a/package-lock.json b/package-lock.json
index 4b08371ee6..5e4592652a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -85,7 +85,7 @@
"license-checker-rseidelsohn": "4.3.0",
"markdownlint-cli": "0.37.0",
"prettier": "3.2.5",
- "sass": "1.32.13",
+ "sass": "1.77.8",
"storybook": "8.1.10",
"ts-node": "10.9.1",
"typescript": "5.5.2",
@@ -14708,6 +14708,13 @@
"url": "https://opencollective.com/immer"
}
},
+ "node_modules/immutable": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
+ "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -20598,18 +20605,21 @@
}
},
"node_modules/sass": {
- "version": "1.32.13",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz",
- "integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==",
+ "version": "1.77.8",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
+ "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "chokidar": ">=3.0.0 <4.0.0"
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
- "node": ">=8.9.0"
+ "node": ">=14.0.0"
}
},
"node_modules/sax": {
diff --git a/package.json b/package.json
index 12347ee644..9654bb8b61 100644
--- a/package.json
+++ b/package.json
@@ -116,7 +116,7 @@
"license-checker-rseidelsohn": "4.3.0",
"markdownlint-cli": "0.37.0",
"prettier": "3.2.5",
- "sass": "1.32.13",
+ "sass": "1.77.8",
"storybook": "8.1.10",
"ts-node": "10.9.1",
"typescript": "5.5.2",
diff --git a/public/themes/dark.json b/public/themes/dark.json
index 6249fc8be4..a3bef2b9bf 100644
--- a/public/themes/dark.json
+++ b/public/themes/dark.json
@@ -13,12 +13,6 @@
"warning": "#F27483",
"text-splitter-hover": "#394152",
"active-point-focus": "#292F38",
- "active-point-hover": "#272A2F",
- "sequencer-whitekey-cell": "#393939",
- "sequencer-blackkey-cell": "#2D2D2D",
- "sequencer-main-divider": "#121212",
- "sequencer-sub-divider": "#2C2C2C",
- "sequencer-white-key": "#EEEEEE",
- "sequencer-black-key": "#555555"
+ "active-point-hover": "#272A2F"
}
}
diff --git a/public/themes/default.json b/public/themes/default.json
index b9ec04f40e..9d04773fd8 100644
--- a/public/themes/default.json
+++ b/public/themes/default.json
@@ -13,12 +13,6 @@
"warning": "#C10015",
"text-splitter-hover": "#CCDDFF",
"active-point-focus": "#E0EAFF",
- "active-point-hover": "#EEF3FF",
- "sequencer-whitekey-cell": "#F5F7F5",
- "sequencer-blackkey-cell": "#EAECEA",
- "sequencer-main-divider": "#B0B0B0",
- "sequencer-sub-divider": "#CECECE",
- "sequencer-white-key": "#FFFFFF",
- "sequencer-black-key": "#333333"
+ "active-point-hover": "#EEF3FF"
}
}
diff --git a/src/components/Dialog/CharacterTryListenCard.vue b/src/components/Dialog/CharacterTryListenCard.vue
index 02e656ff80..d8838da856 100644
--- a/src/components/Dialog/CharacterTryListenCard.vue
+++ b/src/components/Dialog/CharacterTryListenCard.vue
@@ -184,7 +184,7 @@ const rollStyleIndex = (speakerUuid: SpeakerId, diff: number) => {
width: 100%;
height: 100%;
.style-icon {
- $icon-size: vars.$character-item-size / 2;
+ $icon-size: calc(vars.$character-item-size / 2);
width: $icon-size;
height: $icon-size;
border-radius: 5px;
diff --git a/src/components/Dialog/DefaultStyleListDialog.vue b/src/components/Dialog/DefaultStyleListDialog.vue
index 97665abce1..685bc5b653 100644
--- a/src/components/Dialog/DefaultStyleListDialog.vue
+++ b/src/components/Dialog/DefaultStyleListDialog.vue
@@ -276,7 +276,7 @@ const openStyleSelectDialog = (characterInfo: CharacterInfo) => {
width: 100%;
height: 100%;
.style-icon {
- $icon-size: $character-item-size / 2;
+ $icon-size: calc(vars.$character-item-size / 2);
width: $icon-size;
height: $icon-size;
border-radius: 5px;
diff --git a/src/components/Dialog/DefaultStyleSelectDialog.vue b/src/components/Dialog/DefaultStyleSelectDialog.vue
index 252c877355..7f0bcfa405 100644
--- a/src/components/Dialog/DefaultStyleSelectDialog.vue
+++ b/src/components/Dialog/DefaultStyleSelectDialog.vue
@@ -298,7 +298,7 @@ const closeDialog = () => {
width: 100%;
height: 100%;
.style-icon {
- $icon-size: $style-item-size / 2;
+ $icon-size: calc($style-item-size / 2);
width: $icon-size;
height: $icon-size;
border-radius: 5px;
diff --git a/src/components/Sing/CharacterMenuButton/MenuButton.vue b/src/components/Sing/CharacterMenuButton/MenuButton.vue
index e2a08fa357..8a9e8fec82 100644
--- a/src/components/Sing/CharacterMenuButton/MenuButton.vue
+++ b/src/components/Sing/CharacterMenuButton/MenuButton.vue
@@ -38,3 +38,31 @@ const selectedSinger = computed(() => {
return store.getters.SELECTED_TRACK.singer;
});
+
+
diff --git a/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue b/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue
index b2c2287444..926ad94ce8 100644
--- a/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue
+++ b/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue
@@ -1,27 +1,23 @@
-
+
@@ -80,15 +76,34 @@ const selectedStyleIconPath = computed(() => {
diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue
index ec9bb4c12a..8ac011ea4b 100644
--- a/src/components/Sing/ScoreSequencer.vue
+++ b/src/components/Sing/ScoreSequencer.vue
@@ -15,8 +15,10 @@
ref="sequencerBody"
class="sequencer-body"
:class="{
- 'rect-selecting': editTarget === 'NOTE' && shiftKey,
- 'cursor-draw': editTarget === 'PITCH' && !ctrlKey,
+ 'edit-note': editTarget === 'NOTE',
+ 'edit-pitch': editTarget === 'PITCH',
+ previewing: nowPreviewing,
+ [cursorClass]: true,
}"
aria-label="シーケンサ"
@mousedown="onMouseDown"
@@ -28,8 +30,6 @@
@scroll="onScroll"
@contextmenu.prevent
>
-
-
-
+
+
{
return event.target === event.currentTarget;
@@ -233,7 +232,6 @@ const isSelfEventTarget = (event: UIEvent) => {
const { warn } = createLogger("ScoreSequencer");
const store = useStore();
const state = store.state;
-
// 選択中のトラックID
const selectedTrackId = computed(() => store.getters.SELECTED_TRACK_ID);
@@ -368,6 +366,40 @@ const sequencerBody = ref(null);
const cursorX = ref(0);
const cursorY = ref(0);
+const { cursorState, setCursorState, resetCursorState } = useCursorState();
+// カーソルCSSクラス
+const cursorClass = computed(() => {
+ if (editTarget.value === "PITCH") {
+ if (ctrlKey.value) {
+ // ピッチ削除
+ return "cursor-erase";
+ } else {
+ // ピッチ描画
+ return "cursor-draw";
+ }
+ }
+ // 範囲選択
+ if (editTarget.value === "NOTE" && shiftKey.value) {
+ return "cursor-crosshair";
+ }
+ if (cursorState.value === CursorState.EW_RESIZE) {
+ return "cursor-ew-resize";
+ }
+ if (cursorState.value === CursorState.CROSSHAIR) {
+ return "cursor-crosshair";
+ }
+ if (cursorState.value === CursorState.MOVE) {
+ return "cursor-move";
+ }
+ if (cursorState.value === CursorState.DRAW) {
+ return "cursor-draw";
+ }
+ if (cursorState.value === CursorState.ERASE) {
+ return "cursor-erase";
+ }
+ return "";
+});
+
// 歌詞入力
const { previewLyrics, commitPreviewLyrics, splitAndUpdatePreview } =
useLyricInput();
@@ -383,11 +415,11 @@ const onLyricConfirmed = (nextNoteId: NoteId | undefined) => {
// プレビュー
// FIXME: 関連する値を1つのobjectにまとめる
-const nowPreviewing = ref(false);
-let previewMode: PreviewMode = "ADD_NOTE";
+const previewMode = ref("IDLE");
+const nowPreviewing = computed(() => previewMode.value !== "IDLE");
+const executePreviewProcess = ref(false);
let previewRequestId = 0;
let previewStartEditTarget: SequencerEditTarget = "NOTE";
-let executePreviewProcess = false;
// ノート編集のプレビュー
// プレビュー中に更新(移動やリサイズ等)されるノーツ
const previewNotes = ref([]);
@@ -451,6 +483,7 @@ const previewAdd = () => {
const guideLineBaseX = tickToBaseX(noteEndPos, tpqn.value);
guideLineX.value = guideLineBaseX * zoomX.value;
+ setCursorState(CursorState.DRAW);
};
const previewMove = () => {
@@ -504,6 +537,7 @@ const previewMove = () => {
tpqn.value,
);
guideLineX.value = guideLineBaseX * zoomX.value;
+ setCursorState(CursorState.MOVE);
};
const previewResizeRight = () => {
@@ -544,6 +578,7 @@ const previewResizeRight = () => {
const guideLineBaseX = tickToBaseX(newNoteEndPos, tpqn.value);
guideLineX.value = guideLineBaseX * zoomX.value;
+ setCursorState(CursorState.EW_RESIZE);
};
const previewResizeLeft = () => {
@@ -591,6 +626,7 @@ const previewResizeLeft = () => {
const guideLineBaseX = tickToBaseX(newNotePos, tpqn.value);
guideLineX.value = guideLineBaseX * zoomX.value;
+ setCursorState(CursorState.EW_RESIZE);
};
// ピッチを描く処理を行う
@@ -665,6 +701,7 @@ const previewDrawPitch = () => {
previewPitchEdit.value = tempPitchEdit;
prevCursorPos.frame = cursorFrame;
prevCursorPos.frequency = cursorFrequency;
+ setCursorState(CursorState.DRAW);
};
// ドラッグした範囲のピッチ編集データを消去する処理を行う
@@ -697,29 +734,30 @@ const previewErasePitch = () => {
previewPitchEdit.value = tempPitchEdit;
prevCursorPos.frame = cursorFrame;
+ setCursorState(CursorState.ERASE);
};
const preview = () => {
- if (executePreviewProcess) {
- if (previewMode === "ADD_NOTE") {
+ if (executePreviewProcess.value) {
+ if (previewMode.value === "ADD_NOTE") {
previewAdd();
}
- if (previewMode === "MOVE_NOTE") {
+ if (previewMode.value === "MOVE_NOTE") {
previewMove();
}
- if (previewMode === "RESIZE_NOTE_RIGHT") {
+ if (previewMode.value === "RESIZE_NOTE_RIGHT") {
previewResizeRight();
}
- if (previewMode === "RESIZE_NOTE_LEFT") {
+ if (previewMode.value === "RESIZE_NOTE_LEFT") {
previewResizeLeft();
}
- if (previewMode === "DRAW_PITCH") {
+ if (previewMode.value === "DRAW_PITCH") {
previewDrawPitch();
}
- if (previewMode === "ERASE_PITCH") {
+ if (previewMode.value === "ERASE_PITCH") {
previewErasePitch();
}
- executePreviewProcess = false;
+ executePreviewProcess.value = false;
}
previewRequestId = requestAnimationFrame(preview);
};
@@ -808,7 +846,7 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => {
} else if (isOnCommandOrCtrlKeyDown(event)) {
void store.dispatch("SELECT_NOTES", { noteIds: [note.id] });
} else if (!selectedNoteIds.value.has(note.id)) {
- selectOnlyThis(note);
+ void selectOnlyThis(note);
}
for (const note of selectedNotes.value) {
copiedNotes.push({ ...note });
@@ -853,10 +891,9 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => {
} else {
throw new ExhaustiveError(editTarget.value);
}
- previewMode = mode;
+ previewMode.value = mode;
previewStartEditTarget = editTarget.value;
- executePreviewProcess = true;
- nowPreviewing.value = true;
+ executePreviewProcess.value = true;
previewRequestId = requestAnimationFrame(preview);
};
@@ -864,9 +901,8 @@ const endPreview = () => {
cancelAnimationFrame(previewRequestId);
if (previewStartEditTarget === "NOTE") {
// 編集ターゲットがノートのときにプレビューを開始した場合の処理
-
if (edited) {
- if (previewMode === "ADD_NOTE") {
+ if (previewMode.value === "ADD_NOTE") {
void store.dispatch("COMMAND_ADD_NOTES", {
notes: previewNotes.value,
trackId: selectedTrackId.value,
@@ -923,7 +959,8 @@ const endPreview = () => {
} else {
throw new ExhaustiveError(previewStartEditTarget);
}
- nowPreviewing.value = false;
+ previewMode.value = "IDLE";
+ resetCursorState();
};
const onNoteBarMouseDown = (event: MouseEvent, note: Note) => {
@@ -984,6 +1021,7 @@ const onMouseDown = (event: MouseEvent) => {
isRectSelecting.value = true;
rectSelectStartX.value = cursorX.value;
rectSelectStartY.value = cursorY.value;
+ setCursorState(CursorState.CROSSHAIR);
} else {
startPreview(event, "ADD_NOTE");
}
@@ -1012,7 +1050,7 @@ const onMouseMove = (event: MouseEvent) => {
cursorY.value = getYInBorderBox(event.clientY, sequencerBodyElement);
if (nowPreviewing.value) {
- executePreviewProcess = true;
+ executePreviewProcess.value = true;
} else {
const scrollLeft = sequencerBodyElement.scrollLeft;
const cursorBaseX = (scrollLeft + cursorX.value) / zoomX.value;
@@ -1032,6 +1070,7 @@ const onMouseUp = (event: MouseEvent) => {
}
if (isRectSelecting.value) {
rectSelect(isOnCommandOrCtrlKeyDown(event));
+ resetCursorState();
} else if (nowPreviewing.value) {
endPreview();
}
@@ -1442,9 +1481,9 @@ const contextMenuData = computed(() => {
{
type: "button",
label: "コピー",
- onClick: async () => {
+ onClick: () => {
contextMenu.value?.hide();
- await store.dispatch("COPY_NOTES_TO_CLIPBOARD");
+ void store.dispatch("COPY_NOTES_TO_CLIPBOARD");
},
disabled: !isNoteSelected.value,
disableWhenUiLocked: true,
@@ -1452,9 +1491,9 @@ const contextMenuData = computed(() => {
{
type: "button",
label: "切り取り",
- onClick: async () => {
+ onClick: () => {
contextMenu.value?.hide();
- await store.dispatch("COMMAND_CUT_NOTES_TO_CLIPBOARD");
+ void store.dispatch("COMMAND_CUT_NOTES_TO_CLIPBOARD");
},
disabled: !isNoteSelected.value,
disableWhenUiLocked: true,
@@ -1462,9 +1501,9 @@ const contextMenuData = computed(() => {
{
type: "button",
label: "貼り付け",
- onClick: async () => {
+ onClick: () => {
contextMenu.value?.hide();
- await store.dispatch("COMMAND_PASTE_NOTES_FROM_CLIPBOARD");
+ void store.dispatch("COMMAND_PASTE_NOTES_FROM_CLIPBOARD");
},
disableWhenUiLocked: true,
},
@@ -1472,9 +1511,9 @@ const contextMenuData = computed(() => {
{
type: "button",
label: "すべて選択",
- onClick: async () => {
+ onClick: () => {
contextMenu.value?.hide();
- await store.dispatch("SELECT_ALL_NOTES_IN_TRACK", {
+ void store.dispatch("SELECT_ALL_NOTES_IN_TRACK", {
trackId: selectedTrackId.value,
});
},
@@ -1483,9 +1522,9 @@ const contextMenuData = computed(() => {
{
type: "button",
label: "選択解除",
- onClick: async () => {
+ onClick: () => {
contextMenu.value?.hide();
- await store.dispatch("DESELECT_ALL_NOTES");
+ void store.dispatch("DESELECT_ALL_NOTES");
},
disabled: !isNoteSelected.value,
disableWhenUiLocked: true,
@@ -1494,9 +1533,9 @@ const contextMenuData = computed(() => {
{
type: "button",
label: "クオンタイズ",
- onClick: async () => {
+ onClick: () => {
contextMenu.value?.hide();
- await store.dispatch("COMMAND_QUANTIZE_SELECTED_NOTES");
+ void store.dispatch("COMMAND_QUANTIZE_SELECTED_NOTES");
},
disabled: !isNoteSelected.value,
disableWhenUiLocked: true,
@@ -1505,9 +1544,9 @@ const contextMenuData = computed(() => {
{
type: "button",
label: "削除",
- onClick: async () => {
+ onClick: () => {
contextMenu.value?.hide();
- await store.dispatch("COMMAND_REMOVE_SELECTED_NOTES");
+ void store.dispatch("COMMAND_REMOVE_SELECTED_NOTES");
},
disabled: !isNoteSelected.value,
disableWhenUiLocked: true,
@@ -1517,22 +1556,21 @@ const contextMenuData = computed(() => {
diff --git a/src/components/Sing/SequencerGrid.vue b/src/components/Sing/SequencerGrid.vue
index bbfafd1b1e..41f2e16697 100644
--- a/src/components/Sing/SequencerGrid.vue
+++ b/src/components/Sing/SequencerGrid.vue
@@ -1,70 +1,87 @@
-
@@ -112,6 +129,42 @@ const gridWidth = computed(() => {
const gridHeight = computed(() => {
return gridCellHeight.value * keyInfos.length;
});
+// 小節幅
+const measureWidth = computed(() => beatWidth.value * beatsPerMeasure.value);
+// グリッド線の計算
+// オクターブ線や小節線と重なる線は除外
+const gridLines = computed(() => {
+ return keyInfos.reduce<{
+ horizontalLines: number[];
+ octaveLines: number[];
+ }>(
+ (acc, keyInfo, index) => {
+ if (keyInfo.pitch === "F") acc.horizontalLines.push(index);
+ else if (keyInfo.pitch === "C") acc.octaveLines.push(index);
+ return acc;
+ },
+ { horizontalLines: [], octaveLines: [] },
+ );
+});
+const numberOfMeasureLines = computed(() =>
+ Math.ceil(gridWidth.value / measureWidth.value),
+);
+const horizontalLineIndices = computed(() => gridLines.value.horizontalLines);
+const octaveLineIndices = computed(() => gridLines.value.octaveLines);
+const beatLineIndices = computed(() =>
+ Array.from({ length: beatsPerMeasure.value - 1 }, (_, i) => i + 1),
+);
+const snapLinePositions = computed(() => {
+ const snapTicks = gridCellTicks.value;
+ const measureTicks =
+ (tpqn.value * 4 * beatsPerMeasure.value) / timeSignatures.value[0].beatType;
+ const snapCount = Math.floor(measureTicks / snapTicks);
+
+ return Array.from({ length: snapCount }, (_, index) => {
+ const currentTick = snapTicks * index;
+ return (currentTick / measureTicks) * measureWidth.value;
+ });
+});
diff --git a/src/components/Sing/SequencerKeys.vue b/src/components/Sing/SequencerKeys.vue
index 649cdf52b1..cd53e2f7f7 100644
--- a/src/components/Sing/SequencerKeys.vue
+++ b/src/components/Sing/SequencerKeys.vue
@@ -1,15 +1,39 @@
-
@@ -93,21 +128,24 @@ const whiteKeyRects = computed(() => {
const isLast = index === array.length - 1;
const nextValue = isLast ? keyBaseHeight * 128 : array[index + 1];
return {
- x: -2,
- y: Math.floor(value * zoomY.value),
- width: width.value + 2,
+ x: 0,
+ y: Math.round(value * zoomY.value),
+ width: width.value,
height:
- Math.floor(nextValue * zoomY.value) - Math.floor(value * zoomY.value),
+ Math.round(nextValue * zoomY.value) -
+ Math.round(value * zoomY.value) +
+ 1,
};
});
});
+
const blackKeyRects = computed(() => {
return blackKeyBasePositions.map((value) => {
return {
- x: -2,
- y: Math.floor(value * zoomY.value),
- width: props.blackKeyWidth + 2,
- height: Math.floor(keyBaseHeight * zoomY.value),
+ x: 0,
+ y: Math.round(value * zoomY.value),
+ width: props.blackKeyWidth,
+ height: Math.round(keyBaseHeight * zoomY.value),
};
});
});
@@ -170,34 +208,44 @@ onUnmounted(() => {
diff --git a/src/components/Sing/SequencerLyricInput.vue b/src/components/Sing/SequencerLyricInput.vue
index cf39e9baca..183505ae19 100644
--- a/src/components/Sing/SequencerLyricInput.vue
+++ b/src/components/Sing/SequencerLyricInput.vue
@@ -1,18 +1,21 @@
-
-
+ >
+
+
diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue
index df41b34e48..ba2d625828 100644
--- a/src/components/Sing/SequencerNote.vue
+++ b/src/components/Sing/SequencerNote.vue
@@ -1,13 +1,12 @@
+
-
- {{ lyricToDisplay }}
-
+
+
+
+ {{ lyricToDisplay }}
+
diff --git a/src/components/Sing/SequencerPhraseIndicator.vue b/src/components/Sing/SequencerPhraseIndicator.vue
index 4994188884..032dcf9bc4 100644
--- a/src/components/Sing/SequencerPhraseIndicator.vue
+++ b/src/components/Sing/SequencerPhraseIndicator.vue
@@ -49,6 +49,11 @@ const className = computed(() => {
}
.waiting-to-be-rendered {
+ background-color: color-mix(
+ in oklch,
+ var(--scheme-color-secondary-fixed-dim) 70%,
+ var(--scheme-color-background)
+ );
@include tint-if-in-other-track(
"background-color",
color-mix(in srgb, colors.$primary 80%, colors.$background)
@@ -56,8 +61,12 @@ const className = computed(() => {
}
.now-rendering {
+ border: 1px solid --scheme-color-primary-fixed-dim;
+ background-color: var(--scheme-color-background);
+ background-size: 28px 28px;
background-color: colors.$background;
background-size: 28px 28px;
+ animation: stripes-animation 0.7s linear infinite;
&.is-in-selected-track {
border: 1px solid rgba(colors.$primary-rgb, 0.7);
background-image: linear-gradient(
@@ -86,7 +95,6 @@ const className = computed(() => {
tint(rgba(colors.$primary-rgb, 0.36)) 100%
);
}
- animation: stripes-animation 0.7s linear infinite;
}
@keyframes stripes-animation {
@@ -99,7 +107,11 @@ const className = computed(() => {
}
.could-not-render {
- @include tint-if-in-other-track("background-color", colors.$warning);
+ background-color: var(--scheme-color-error);
+ @include tint-if-in-other-track(
+ "background-color",
+ var(--scheme-color-error)
+ );
}
.playable {
diff --git a/src/components/Sing/SequencerPitch.vue b/src/components/Sing/SequencerPitch.vue
index a5d911cc1e..5918020987 100644
--- a/src/components/Sing/SequencerPitch.vue
+++ b/src/components/Sing/SequencerPitch.vue
@@ -3,7 +3,7 @@
diff --git a/src/components/Sing/SequencerShadowNote.vue b/src/components/Sing/SequencerShadowNote.vue
index 988717451a..3ee27ec639 100644
--- a/src/components/Sing/SequencerShadowNote.vue
+++ b/src/components/Sing/SequencerShadowNote.vue
@@ -60,13 +60,14 @@ const width = computed(() => {
pointer-events: none;
.shadow-line {
+ border-radius: 1px;
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 2px;
transform: translateY(-50%);
- background-color: colors.$sequencer-main-divider;
+ background-color: var(--scheme-color-sing-shadow-note);
}
}
diff --git a/src/components/Sing/ToolBar/EditTargetSwicher.vue b/src/components/Sing/ToolBar/EditTargetSwicher.vue
index f6c74f9579..70d91d118b 100644
--- a/src/components/Sing/ToolBar/EditTargetSwicher.vue
+++ b/src/components/Sing/ToolBar/EditTargetSwicher.vue
@@ -1,37 +1,43 @@
-
-
- ノート編集
-
-
- ピッチ編集
{{ !isMac ? "Ctrl" : "Cmd" }}+クリックで消去
+
+
+
+
+ ノート編集
+
+
+
+
+
+
+
-
-
+ ピッチ編集
{{ !isMac ? "Ctrl" : "Cmd" }}+クリックで消去
+
+
+
-
diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue
index f717403fc8..8eaef4a4a6 100644
--- a/src/components/Sing/ToolBar/ToolBar.vue
+++ b/src/components/Sing/ToolBar/ToolBar.vue
@@ -4,70 +4,92 @@
-
-
-
-
-
-
-
-
@@ -81,14 +103,14 @@
@@ -106,31 +128,33 @@
flat
dense
round
- icon="undo"
class="sing-undo-button"
:disable="!canUndo"
@click="undo"
- />
+ >
+
+
+ >
+
+
-
+
store.getters.SELECTED_TRACK_ID);
+const beatsOptions = computed(() => {
+ return Array.from({ length: 32 }, (_, i) => ({
+ label: (i + 1).toString(),
+ value: i + 1,
+ }));
+});
+
+const beatTypeOptions = computed(() => {
+ return [2, 4, 8, 16, 32].map((beatType) => ({
+ label: beatType.toString(),
+ value: beatType,
+ }));
+});
+
const bpmInputBuffer = ref(120);
const beatsInputBuffer = ref(4);
const beatTypeInputBuffer = ref(4);
@@ -278,20 +316,30 @@ const setBpmInputBuffer = (bpmStr: string | number | null) => {
bpmInputBuffer.value = bpmValue;
};
-const setBeatsInputBuffer = (beatsStr: string | number | null) => {
- const beatsValue = Number(beatsStr);
- if (!isValidBeats(beatsValue)) {
+const setBeats = (beats: { label: string; value: number }) => {
+ if (!isValidBeats(beats.value)) {
return;
}
- beatsInputBuffer.value = beatsValue;
+ void store.dispatch("COMMAND_SET_TIME_SIGNATURE", {
+ timeSignature: {
+ measureNumber: 1,
+ beats: beats.value,
+ beatType: timeSignatures.value[0].beatType,
+ },
+ });
};
-const setBeatTypeInputBuffer = (beatTypeStr: string | number | null) => {
- const beatTypeValue = Number(beatTypeStr);
- if (!isValidBeatType(beatTypeValue)) {
+const setBeatType = (beatType: { label: string; value: number }) => {
+ if (!isValidBeatType(beatType.value)) {
return;
}
- beatTypeInputBuffer.value = beatTypeValue;
+ void store.dispatch("COMMAND_SET_TIME_SIGNATURE", {
+ timeSignature: {
+ measureNumber: 1,
+ beats: timeSignatures.value[0].beats,
+ beatType: beatType.value,
+ },
+ });
};
const setKeyRangeAdjustmentInputBuffer = (
@@ -324,18 +372,6 @@ const setTempo = () => {
});
};
-const setTimeSignature = () => {
- const beats = beatsInputBuffer.value;
- const beatType = beatTypeInputBuffer.value;
- void store.dispatch("COMMAND_SET_TIME_SIGNATURE", {
- timeSignature: {
- measureNumber: 1,
- beats,
- beatType,
- },
- });
-};
-
const setKeyRangeAdjustment = () => {
const keyRangeAdjustment = keyRangeAdjustmentInputBuffer.value;
void store.dispatch("COMMAND_SET_KEY_RANGE_ADJUSTMENT", {
@@ -451,29 +487,80 @@ onUnmounted(() => {
diff --git a/src/components/Talk/AudioCell.vue b/src/components/Talk/AudioCell.vue
index 63d957739e..e7fbd99a51 100644
--- a/src/components/Talk/AudioCell.vue
+++ b/src/components/Talk/AudioCell.vue
@@ -679,6 +679,7 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1);
position: relative;
padding: 0.4rem 0.5rem;
margin: 0.2rem 0.5rem;
+ gap: 0px 1rem;
&:focus {
// divはフォーカスするとデフォルトで青い枠が出るので消す
outline: none;
@@ -695,8 +696,6 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1);
margin-bottom: 0.6rem;
}
- gap: 0px 1rem;
-
.active-arrow {
left: -1rem;
height: 2rem;
diff --git a/src/composables/useCursorState.ts b/src/composables/useCursorState.ts
new file mode 100644
index 0000000000..bf7f716538
--- /dev/null
+++ b/src/composables/useCursorState.ts
@@ -0,0 +1,48 @@
+import { ref, computed } from "vue";
+
+// カーソル状態
+export enum CursorState {
+ UNSET = "unset",
+ EW_RESIZE = "ew-resize",
+ MOVE = "move",
+ CROSSHAIR = "crosshair",
+ DRAW = "draw",
+ ERASE = "erase",
+}
+
+// カーソル状態を管理するカスタムコンポーザブル
+export const useCursorState = () => {
+ const cursorState = ref(CursorState.UNSET);
+
+ const cursorClass = computed(() => {
+ switch (cursorState.value) {
+ case CursorState.EW_RESIZE:
+ return "cursor-ew-resize";
+ case CursorState.CROSSHAIR:
+ return "cursor-crosshair";
+ case CursorState.MOVE:
+ return "cursor-move";
+ case CursorState.DRAW:
+ return "cursor-draw";
+ case CursorState.ERASE:
+ return "cursor-erase";
+ default:
+ return "";
+ }
+ });
+
+ const setCursorState = (state: CursorState) => {
+ cursorState.value = state;
+ };
+
+ const resetCursorState = () => {
+ cursorState.value = CursorState.UNSET;
+ };
+
+ return {
+ cursorState,
+ cursorClass,
+ setCursorState,
+ resetCursorState,
+ };
+};
diff --git a/src/sing/viewHelper.ts b/src/sing/viewHelper.ts
index 2cf9ed2c7a..106616a473 100644
--- a/src/sing/viewHelper.ts
+++ b/src/sing/viewHelper.ts
@@ -134,6 +134,15 @@ export async function calculatePitchDataHash(pitchData: PitchData) {
export type MouseButton = "LEFT_BUTTON" | "RIGHT_BUTTON" | "OTHER_BUTTON";
+export type PreviewMode =
+ | "IDLE"
+ | "ADD_NOTE"
+ | "MOVE_NOTE"
+ | "RESIZE_NOTE_RIGHT"
+ | "RESIZE_NOTE_LEFT"
+ | "DRAW_PITCH"
+ | "ERASE_PITCH";
+
export function getButton(event: MouseEvent): MouseButton {
// macOSの場合、Ctrl+クリックは右クリック
if (isMac && event.button === 0 && event.ctrlKey) {
diff --git a/src/styles/_index.scss b/src/styles/_index.scss
index dad189293b..d12e38a1a6 100644
--- a/src/styles/_index.scss
+++ b/src/styles/_index.scss
@@ -2,6 +2,8 @@
@use './colors' as colors;
@use './v2/variables' as vars-v2;
@use './v2/colors' as colors-v2;
+@use './v2/sing-colors'; // ソング用カラー
+@use './v2/cursor'; // カーソル
@import "./fonts";
// 優先度を強引に上げる
diff --git a/src/styles/v2/cursor.scss b/src/styles/v2/cursor.scss
new file mode 100644
index 0000000000..b706c98022
--- /dev/null
+++ b/src/styles/v2/cursor.scss
@@ -0,0 +1,25 @@
+.cursor-default {
+ cursor: default;
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
+
+.cursor-crosshair {
+ cursor: crosshair;
+}
+
+.cursor-ew-resize {
+ cursor: ew-resize;
+}
+
+.cursor-move {
+ cursor: move;
+}
+
+.cursor-draw {
+ cursor:
+ url("/draw-cursor.png") 2 30,
+ auto;
+}
\ No newline at end of file
diff --git a/src/styles/v2/sing-colors.scss b/src/styles/v2/sing-colors.scss
new file mode 100644
index 0000000000..96c4189d30
--- /dev/null
+++ b/src/styles/v2/sing-colors.scss
@@ -0,0 +1,674 @@
+/*
+
+## 利用
+
+基本的にはMaterialDesign3のカラーシステムに近い考え方であり
+おおざっぱなロールごと(primary/secondaryなど)のベース彩度+色相は固定し、
+詳細なロール(on-primary/secondary-containerなど)に応じて明度のみ変更する形です
+
+https://m3.material.io/styles/color/roles
+
+## 各ロール
+
+- primary:
+主要・強調される操作可能部のカラー・ブランドカラーです
+
+- secondary:
+主要でない操作可能部のカラーです
+
+- tertiary:
+現時点での利用部分想定としては一時的・未決定部などでプライマリ・セカンダリで表現しきれない場合に利用します
+eg: ノートのプレビュー / 一時的に全ソロのインジケータ / シンガー発音時インジケータ / 失敗がありうる場合の待ちなど
+
+- error:
+エラー・警告です
+
+- neutral
+背景、テキスト、境界線など、UI全体で使用される中立的なカラーです
+ユーザーの注意を引くことなく、背景やサポート的な役割を果たす要素です
+
+- neutral-variant
+背景、セクションの区切り、低コントラストなUI要素などに利用します
+操作可能なUI要素のカラーです
+
+- link
+外部リンクなどのリンクを表すカラーです
+
+## カラースキームの指定
+
+以下のようにCSS変数を指定します
+
+background-color: var(--scheme-color-primary);
+color: var(--scheme-color-on-primary);
+
+## カスタマイズサンプル
+
+OKLab(OKLCH)で指定します
+
+l: 明度 / c: 彩度 / h: 色相
+
+50%明度プライマリを指定:
+ color: oklch(var(--lr-50) var(--primary-c) var(--primary-h));
+
+50%明度プライマリを指定: CSS Variables+相対CSSの場合
+ color: oklch(from var(--primary-key) l calc(var(--lr-50)));
+
+ロールカラー指定: --on-primaryのdarkテーマの値を指定したサンプル
+ --on-primary-dark: oklch(var(--lr-10) var(--primary-c) var(--primary-h));
+
+エイリアス指定: 特定のロール(この場合はneutral)だが明度が違う
+ --sing-piano-key-white-dark: oklch(var(--lr-70) var(--neutral-c) var(--neutral-h));
+
+プライマリと色調を揃えた新規カラー指定: (色相が150度異なる紫になる)
+ color: oklch(from var(--primary-key) l calc(var(--lr-60)) c h calc(var(--primary-c) + 150)));
+
+---
+
+TODO: ほとんどの場合、
+
+- role/on-role/role-container/on-role-containerの4つ
+- 必要あればそれぞれのhover/active/focus/disabledの状態
+
+のスタイルを指定することになるため、明度ルールが安定したらmixin化するか検討する。
+
+eg: 常にホバー時は+5%明度上げるのであれば
+&:hover: oklch(from var(--primary) l calc(...lr-50をlr-55にするmixin));
+
+*/
+
+@use "sass:math";
+
+/*
+カラースキーム計算基準
+*/
+:root {
+ // 基準彩度
+ --primary-c: 0.098;
+ // 元実装(v0.20.0)のprimary(light) #a5d4ad: OKLCH彩度 0.073
+ // 元実装(v0.20.0)のprimary(dark): #86C591: OKLCH彩度 0.098
+ // たとえば元のprimary(light)に揃えたいならここを0.073にする
+ --secondary-c: 0.036;
+ --tertiary-c: var(--primary-c);
+ --error-c: 0.196;
+ --neutral-c: 0.003;
+ --neutral-variant-c: 0.006;
+ --link-c: 0.196;
+
+ // 基準色相
+ --primary-h: 149.64; // VOICEVOXプライマリ #a5d4adの色相
+ --secondary-h: var(--primary-h);
+ --tertiary-h: calc(var(--primary-h) - 60); // -60度
+ --error-h: 19.64;
+ --neutral-h: calc(var(--primary-h) + 120);
+ --neutral-variant-h: var(--primary-h);
+ --link-h: 256; // `styles/v2/colors.scss` におけるprimitive-blueの色相
+}
+
+/*
+明度LUT
+OKLCHのL値を相対明度(おおむねガンマ補正2.2に近い)に補正した%単位値
+そのままのL値を使うとディスプレイにおいては知覚的にリニアな明るさにならないため補正しています
+eg: 50%: --lr-50
+
+参考: https://bottosson.github.io/posts/colorpicker/#intermission---a-new-lightness-estimate-for-oklab
+参考議論: https://github.com/VOICEVOX/voicevox/issues/1810#issuecomment-2260529835
+参考議論: https://github.com/VOICEVOX/voicevox/issues/1810#issuecomment-2260631491
+*/
+
+@function LtoLr($L) {
+ $k1: 0.206;
+ $k2: 0.03;
+ $k3: calc((1 + $k2) / (1 + $k1));
+
+ $l: calc($L / 100);
+ $Lr: calc(
+ (
+ $k3 * $l - $k2 +
+ math.sqrt(math.pow($k3 * $l - $k2, 2) + 4 * $k1 * $k3 * $l)
+ ) / 2
+ );
+ @return $Lr;
+}
+
+/*
+明度LUTをCSS変数として定義
+*/
+:root {
+ @for $i from 0 through 100 {
+ $L: $i;
+ $Lr: LtoLr($L);
+ --lr-#{$i}: #{calc(($Lr * 10000) / 10000)}; // 小数点以下4桁
+ }
+}
+
+/*
+OKLCHでのカラースキーム
+他の定義と混ざらないようにするため、prefixとして--scheme-color-をつけています
+
+eg: セカンダリコンテナ(90%明度)
+--scheme-color-secondary-container: oklch(var(--lr-90) var(--secondary-c) var(--secondary-h));
+
+FIXME:
+SASSのmapなどで構造化+mixin一括処理などで処理可能ですが、
+リファクタリング課題(元が動的生成用のTypeScriptコードのため)
+*/
+
+/*
+ライトテーマ
+*/
+:root[is-dark-theme="false"] {
+ --scheme-color-primary: oklch(var(--lr-40) var(--primary-c) var(--primary-h));
+ --scheme-color-on-primary: oklch(
+ var(--lr-100) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-primary-container: oklch(
+ var(--lr-90) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-on-primary-container: oklch(
+ var(--lr-10) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-primary-fixed: oklch(
+ var(--lr-90) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-primary-fixed-dim: oklch(
+ var(--lr-80) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-on-primary-fixed: oklch(
+ var(--lr-10) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-on-primary-fixed-variant: oklch(
+ var(--lr-30) var(--primary-c) var(--primary-h)
+ );
+
+ --scheme-color-secondary: oklch(
+ var(--lr-40) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary: oklch(
+ var(--lr-100) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-secondary-container: oklch(
+ var(--lr-90) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary-container: oklch(
+ var(--lr-10) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-secondary-fixed: oklch(
+ var(--lr-90) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-secondary-fixed-dim: oklch(
+ var(--lr-80) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary-fixed: oklch(
+ var(--lr-10) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary-fixed-variant: oklch(
+ var(--lr-30) var(--secondary-c) var(--secondary-h)
+ );
+
+ --scheme-color-tertiary: oklch(
+ var(--lr-40) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary: oklch(
+ var(--lr-100) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-tertiary-container: oklch(
+ var(--lr-90) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary-container: oklch(
+ var(--lr-10) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-tertiary-fixed: oklch(
+ var(--lr-90) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-tertiary-fixed-dim: oklch(
+ var(--lr-80) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary-fixed: oklch(
+ var(--lr-10) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary-fixed-variant: oklch(
+ var(--lr-30) var(--tertiary-c) var(--tertiary-h)
+ );
+
+ --scheme-color-error: oklch(var(--lr-40) var(--error-c) var(--error-h));
+ --scheme-color-on-error: oklch(var(--lr-100) var(--error-c) var(--error-h));
+ --scheme-color-error-container: oklch(
+ var(--lr-90) var(--error-c) var(--error-h)
+ );
+ --scheme-color-on-error-container: oklch(
+ var(--lr-10) var(--error-c) var(--error-h)
+ );
+
+ --scheme-color-background: oklch(
+ var(--lr-99) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-on-background: oklch(
+ var(--lr-10) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface: oklch(var(--lr-99) var(--neutral-c) var(--neutral-h));
+ --scheme-color-on-surface: oklch(
+ var(--lr-10) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-variant: oklch(
+ var(--lr-90) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-on-surface-variant: oklch(
+ var(--lr-30) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-outline: oklch(
+ var(--lr-50) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-outline-variant: oklch(
+ var(--lr-80) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+
+ --scheme-color-shadow: oklch(var(--lr-0) var(--neutral-c) var(--neutral-h));
+ --scheme-color-scrim: oklch(var(--lr-0) var(--neutral-c) var(--neutral-h));
+ --scheme-color-inverse-surface: oklch(
+ var(--lr-20) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-inverse-on-surface: oklch(
+ var(--lr-95) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-inverse-primary: oklch(
+ var(--lr-80) var(--primary-c) var(--primary-h)
+ );
+
+ --scheme-color-surface-dim: oklch(
+ var(--lr-87) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-bright: oklch(
+ var(--lr-98) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-lowest: oklch(
+ var(--lr-90) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-low: oklch(
+ var(--lr-92) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container: oklch(
+ var(--lr-94) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-high: oklch(
+ var(--lr-96) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-highest: oklch(
+ var(--lr-100) var(--neutral-c) var(--neutral-h)
+ );
+
+ /* カスタムカラー */
+ --scheme-color-link: oklch(var(--lr-40) var(--link-c) var(--link-h));
+ --scheme-color-on-link: oklch(var(--lr-100) var(--link-c) var(--link-h));
+ --scheme-color-link-container: oklch(
+ var(--lr-90) var(--link-c) var(--link-h)
+ );
+ --scheme-color-on-link-container: oklch(
+ var(--lr-10) var(--link-c) var(--link-h)
+ );
+
+ /* ソング用エイリアスカラー */
+ --scheme-color-sing-toolbar-container: oklch(
+ var(--lr-99) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-playback-button-container: oklch(
+ var(--lr-80) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-on-playback-button-container: oklch(
+ var(--lr-10) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-grid-cell-white: oklch(
+ var(--lr-100) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-grid-cell-black: oklch(
+ var(--lr-96) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-grid-measure-line: oklch(
+ var(--lr-75) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-grid-beat-line: oklch(
+ var(--lr-85) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-grid-vertical-line: oklch(
+ var(--lr-94) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-grid-octave-line: oklch(
+ var(--lr-80) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-grid-horizontal-line: oklch(
+ var(--lr-94) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-ruler-surface: oklch(
+ var(--lr-90) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-ruler-measure-line: oklch(
+ var(--lr-65) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-ruler-beat-line: oklch(
+ var(--lr-75) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-piano-key-white: oklch(
+ var(--lr-99) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-piano-key-black: oklch(
+ var(--lr-45) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-piano-keys-right-border: oklch(
+ var(--lr-70) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-note-bar-container: oklch(
+ var(--lr-90) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-sing-on-note-bar-container: oklch(
+ var(--lr-10) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-sing-note-bar-border: oklch(
+ var(--lr-60) var(--secondary-c) var(--secondary-h)
+ );
+
+ --scheme-color-sing-note-bar-selected-container: oklch(
+ var(--lr-96) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-on-note-bar-selected-container: oklch(
+ var(--lr-10) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-note-bar-selected-border: oklch(
+ var(--lr-60) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-note-bar-selected-outline: oklch(
+ var(--lr-80) var(--primary-c) var(--primary-h)
+ );
+
+ --scheme-color-sing-note-bar-below-pitch-container: oklch(
+ var(--lr-90) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-sing-note-bar-below-pitch-invalid-container: oklch(
+ var(--lr-90) var(--secondary-c) var(--error-h)
+ );
+
+ --scheme-color-sing-note-bar-preview-container: oklch(
+ var(--lr-90) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-sing-on-note-bar-preview-container: oklch(
+ var(--lr-10) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-sing-note-bar-preview-border: oklch(
+ var(--lr-60) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-sing-note-bar-preview-outline: oklch(
+ var(--lr-80) var(--tertiary-c) var(--tertiary-h)
+ );
+
+ --scheme-color-sing-shadow-note: oklch(
+ var(--lr-84) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+}
+
+/* ダークテーマ */
+:root[is-dark-theme="true"] {
+ --scheme-color-primary: oklch(var(--lr-80) var(--primary-c) var(--primary-h));
+ --scheme-color-on-primary: oklch(
+ var(--lr-20) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-primary-container: oklch(
+ var(--lr-30) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-on-primary-container: oklch(
+ var(--lr-90) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-primary-fixed: oklch(
+ var(--lr-90) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-primary-fixed-dim: oklch(
+ var(--lr-80) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-on-primary-fixed: oklch(
+ var(--lr-10) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-on-primary-fixed-variant: oklch(
+ var(--lr-30) var(--primary-c) var(--primary-h)
+ );
+
+ --scheme-color-secondary: oklch(
+ var(--lr-80) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary: oklch(
+ var(--lr-20) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-secondary-container: oklch(
+ var(--lr-30) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary-container: oklch(
+ var(--lr-90) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-secondary-fixed: oklch(
+ var(--lr-90) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-secondary-fixed-dim: oklch(
+ var(--lr-80) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary-fixed: oklch(
+ var(--lr-10) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-on-secondary-fixed-variant: oklch(
+ var(--lr-30) var(--secondary-c) var(--secondary-h)
+ );
+
+ --scheme-color-tertiary: oklch(
+ var(--lr-80) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary: oklch(
+ var(--lr-20) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-tertiary-container: oklch(
+ var(--lr-30) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary-container: oklch(
+ var(--lr-90) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-tertiary-fixed: oklch(
+ var(--lr-90) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-tertiary-fixed-dim: oklch(
+ var(--lr-80) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary-fixed: oklch(
+ var(--lr-10) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-on-tertiary-fixed-variant: oklch(
+ var(--lr-30) var(--tertiary-c) var(--tertiary-h)
+ );
+
+ --scheme-color-error: oklch(var(--lr-80) var(--error-c) var(--error-h));
+ --scheme-color-on-error: oklch(var(--lr-20) var(--error-c) var(--error-h));
+ --scheme-color-error-container: oklch(
+ var(--lr-30) var(--error-c) var(--error-h)
+ );
+ --scheme-color-on-error-container: oklch(
+ var(--lr-90) var(--error-c) var(--error-h)
+ );
+
+ --scheme-color-shadow: oklch(var(--lr-0) var(--neutral-c) var(--neutral-h));
+ --scheme-color-scrim: oklch(var(--lr-0) var(--neutral-c) var(--neutral-h));
+ --scheme-color-inverse-surface: oklch(
+ var(--lr-90) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-inverse-on-surface: oklch(
+ var(--lr-20) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-inverse-primary: oklch(
+ var(--lr-40) var(--primary-c) var(--primary-h)
+ );
+
+ --scheme-color-background: oklch(
+ var(--lr-10) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-on-background: oklch(
+ var(--lr-90) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface: oklch(var(--lr-6) var(--neutral-c) var(--neutral-h));
+ --scheme-color-on-surface: oklch(
+ var(--lr-90) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-variant: oklch(
+ var(--lr-30) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-on-surface-variant: oklch(
+ var(--lr-80) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-outline: oklch(
+ var(--lr-60) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-outline-variant: oklch(
+ var(--lr-30) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+
+ --scheme-color-surface-dim: oklch(
+ var(--lr-6) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-bright: oklch(
+ var(--lr-24) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-lowest: oklch(
+ var(--lr-4) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-low: oklch(
+ var(--lr-10) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container: oklch(
+ var(--lr-12) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-high: oklch(
+ var(--lr-17) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-surface-container-highest: oklch(
+ var(--lr-24) var(--neutral-c) var(--neutral-h)
+ );
+
+ /* カスタムカラー */
+ --scheme-color-link: oklch(var(--lr-80) var(--link-c) var(--link-h));
+ --scheme-color-on-link: oklch(var(--lr-20) var(--link-c) var(--link-h));
+ --scheme-color-link-container: oklch(
+ var(--lr-30) var(--link-c) var(--link-h)
+ );
+ --scheme-color-on-link-container: oklch(
+ var(--lr-90) var(--link-c) var(--link-h)
+ );
+
+ /* ソング用エイリアスカラー */
+ --scheme-color-sing-toolbar-container: oklch(
+ var(--lr-17) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-playback-button-container: oklch(
+ var(--lr-70) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-on-playback-button-container: oklch(
+ var(--lr-10) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-grid-cell-white: oklch(
+ var(--lr-20) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-grid-cell-black: oklch(
+ var(--lr-16) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-grid-measure-line: oklch(
+ var(--lr-40) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-grid-beat-line: oklch(
+ var(--lr-4) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-grid-vertical-line: oklch(
+ var(--lr-14) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-grid-octave-line: oklch(
+ var(--lr-4) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-grid-horizontal-line: oklch(
+ var(--lr-14) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-ruler-surface: oklch(
+ var(--lr-30) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-ruler-measure-line: oklch(
+ var(--lr-60) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-ruler-beat-line: oklch(
+ var(--lr-50) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-piano-key-white: oklch(
+ var(--lr-70) var(--neutral-c) var(--neutral-h)
+ );
+ --scheme-color-sing-piano-key-black: oklch(
+ var(--lr-20) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-piano-keys-right-border: oklch(
+ var(--lr-35) var(--neutral-c) var(--neutral-h)
+ );
+
+ --scheme-color-sing-note-bar-container: oklch(
+ var(--lr-70) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-sing-on-note-bar-container: oklch(
+ var(--lr-10) var(--secondary-c) var(--secondary-h)
+ );
+ --scheme-color-sing-note-bar-border: oklch(
+ var(--lr-85) var(--secondary-c) var(--secondary-h)
+ );
+
+ --scheme-color-sing-note-bar-selected-container: oklch(
+ var(--lr-80) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-on-note-bar-selected-container: oklch(
+ var(--lr-10) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-note-bar-selected-border: oklch(
+ var(--lr-95) var(--primary-c) var(--primary-h)
+ );
+ --scheme-color-sing-note-bar-selected-outline: oklch(
+ var(--lr-60) var(--primary-c) var(--primary-h)
+ );
+
+ --scheme-color-sing-note-bar-error-container: oklch(
+ var(--lr-90) var(--secondary-c) var(--error-h)
+ );
+ --scheme-color-sing-on-note-bar-error-container: oklch(
+ var(--lr-10) var(--secondary-c) var(--error-h)
+ );
+
+ --scheme-color-sing-note-bar-below-pitch-container: oklch(
+ var(--lr-27) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+ --scheme-color-sing-note-bar-below-pitch-invalid-container: oklch(
+ var(--lr-27) var(--secondary-c) var(--error-h)
+ );
+
+ --scheme-color-sing-note-bar-preview-container: oklch(
+ var(--lr-70) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-sing-on-note-bar-preview-container: oklch(
+ var(--lr-10) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-sing-note-bar-preview-border: oklch(
+ var(--lr-85) var(--tertiary-c) var(--tertiary-h)
+ );
+ --scheme-color-sing-note-bar-preview-outline: oklch(
+ var(--lr-60) var(--tertiary-c) var(--tertiary-h)
+ );
+
+ --scheme-color-sing-shadow-note: oklch(
+ var(--lr-40) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
+}
diff --git a/src/styles/v2/variables.scss b/src/styles/v2/variables.scss
index a81a396bf8..6018a9a5f6 100644
--- a/src/styles/v2/variables.scss
+++ b/src/styles/v2/variables.scss
@@ -24,3 +24,14 @@ $radius-2: calc(var(--radius-basis) * 2);
$transition-duration: 100ms;
$z-index-tooltip: 9999;
+
+$z-index-dropdown: 1000;
+$z-index-fixed: 1020;
+$z-index-dialog: 1050;
+
+$z-index-sing-background: 0;
+$z-index-sing-note: 10;
+$z-index-sing-note-lyric: 20;
+$z-index-sing-pitch: 30;
+$z-index-sing-playhead: 40;
+$z-index-sing-lyric-input: 50;
diff --git "a/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts" "b/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts"
index b0f6dfb1c8..01b7b6ee31 100644
--- "a/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts"
+++ "b/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts"
@@ -1,4 +1,4 @@
-import { test, expect, Page, Locator } from "@playwright/test";
+import { test, expect, Page } from "@playwright/test";
import { gotoHome, navigateToMain } from "../../navigators";
@@ -69,18 +69,34 @@ test("ダブルクリックで歌詞を編集できる", async ({ page }) => {
const sequencer = page.getByLabel("シーケンサ");
- const getCurrentNoteLyric = async (note: Locator) =>
- await note.getByTestId("note-lyric").textContent();
+ const getCurrentNoteLyric = async () =>
+ await sequencer.locator(".note-lyric").first().textContent();
+ // ノートを追加し、表示されるまで待つ
await sequencer.click({ position: { x: 107, y: 171 } });
+ await page.waitForSelector(".note");
- const note = sequencer.locator(".note");
- const beforeLyric = await getCurrentNoteLyric(note);
+ // ノートの歌詞を取得
+ const note = sequencer.locator(".note").first();
+ const beforeLyric = await getCurrentNoteLyric();
- await sequencer.click({ position: { x: 107, y: 171 }, clickCount: 2 }); // ダブルクリック
+ // ノートをダブルクリックし、入力フィールドが表示されるまで待つ
+ await note.dblclick();
+ await page.waitForSelector(".lyric-input");
- await sequencer.locator(".lyric-input").fill("あ");
- await page.keyboard.press("Enter");
- const afterLyric = await getCurrentNoteLyric(note);
+ // 歌詞を入力し、Enterキーを押す
+ const lyricInput = sequencer.locator(".lyric-input");
+ await lyricInput.fill("あ");
+ await lyricInput.press("Enter");
+
+ // 変更が反映されるまで待つ
+ await page.waitForFunction(() => {
+ const lyricElement = document.querySelector(".note-lyric");
+ return lyricElement && lyricElement.textContent === "あ";
+ });
+
+ // 歌詞が変更されたことを確認
+ const afterLyric = await getCurrentNoteLyric();
expect(afterLyric).not.toEqual(beforeLyric);
+ expect(afterLyric).toEqual("あ");
});