From 56ef6ee8392c082ef13a5286dbbe7a4818cbea42 Mon Sep 17 00:00:00 2001 From: Romot Date: Sat, 12 Oct 2024 16:22:03 +0900 Subject: [PATCH] =?UTF-8?q?#2039=20=E6=93=8D=E4=BD=9C=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=E9=81=B8=E3=81=B9=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B=E3=83=A2=E3=83=83=E3=82=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ScoreSequencer.vue | 641 ++++++++++++++++++++++--- 1 file changed, 586 insertions(+), 55 deletions(-) diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 6e055cd95d..02928d1054 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -26,6 +26,7 @@ @mouseup="onMouseUp" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" + @dblclick.stop="onDoubleClick" @wheel="onWheel" @scroll="onScroll" @contextmenu.prevent @@ -159,6 +160,63 @@ ref="contextMenu" :menudata="contextMenuData" /> + +
+ +
+ +
+ + + + + + +
+ +
+ + + + + + + + + +
@@ -233,6 +291,366 @@ const isSelfEventTarget = (event: UIEvent) => { const { warn } = createLogger("ScoreSequencer"); const store = useStore(); const state = store.state; +/* + +# 単一ツールのみで操作 + +## TOOLLESS_A +### マルチツール +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|ノート追加| +|ノート追加後|該当ノートの選択| +|ノート選択中にCtrl or Command + グリッドをクリック|すべて選択解除| +|グリッドをダブルクリック|なし| +|グリッドをドラッグ|ノート追加| +|グリッドドラッグ後|該当ノートの選択| +|Shift + グリッドをドラッグ|矩形選択| +|Shift + グリッドドラッグ後|矩形選択中のノートの選択| +|ノートをクリック|ノート単体選択| +|ノート選択中にCtrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートの選択| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートの選択| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| + +## TOOLLESS_B +### マルチツール +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|ノート追加| +|ノート追加後|該当ノートを選択しない| +|ノート選択中にグリッドをクリック|すべて選択解除・ノートの追加はしない| +|グリッドをダブルクリック|なし| +|グリッドをドラッグ|ノート追加| +|グリッドドラッグ後|該当ノートを選択しない| +|Shift + グリッドをドラッグ|矩形選択| +|ノートをクリック|ノート単体選択| +|Ctrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートを選択しない| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートを選択しない| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| + +## TOOLLESS_C +### マルチツール +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|ノート追加| +|ノート追加後|該当ノートを選択しない| +|ノート選択中にグリッドをクリック|すべて選択解除| +|グリッドをダブルクリック|なし| +|グリッドをドラッグ|ノート追加| +|グリッドドラッグ後|該当ノートを選択しない| +|Shift + グリッドをドラッグ|矩形選択| +|ノートをクリック|ノート単体選択| +|Ctrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートを選択する| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートを選択する| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| + +## TOOLLESS_D +### マルチツール +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|すべて選択解除| +|ノート選択中にグリッドをクリック|すべて選択解除| +|グリッドをダブルクリック|ノート追加| +|ノート追加後|該当ノートを選択する| +|グリッドをドラッグ|矩形選択| +|グリッドドラッグ後|矩形選択中のノートの選択| +|Shift + グリッドをドラッグ|なし| +|ノートをクリック|ノート単体選択| +|Ctrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートを選択する| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートを選択する| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| + +# ツールパレットからツールを選択して操作 +## TOOLSELECT_A +### マルチツール1(選択優先) +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|すべて選択解除| +|ノート選択中にグリッドをクリック|すべて選択解除| +|グリッドをダブルクリック|ノート追加| +|ノート追加後|該当ノートを選択する| +|グリッドをドラッグ|矩形選択| +|グリッドドラッグ後|選択中のノートの選択| +|Shift + グリッドをドラッグ|矩形選択| +|ノートをクリック|ノート単体選択| +|Ctrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートを選択する| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートを選択する| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| +### マルチエディットツール2(編集優先) +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|ノート追加| +|ノート選択中にグリッドをクリック|ノート追加| +|グリッドをダブルクリック|なし| +|ノート追加後|該当ノートを選択しない| +|グリッドをドラッグ|ノート追加| +|グリッドドラッグ後|選択中のノートを選択しない| +|Shift + グリッドをドラッグ|矩形選択| +|ノートをクリック|ノート単体選択| +|Ctrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートを選択する| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートを選択しない| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| + +## TOOLSELECT_B +### 選択ツール +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|すべて選択解除| +|ノート選択中にグリッドをクリック|すべて選択解除| +|グリッドをダブルクリック|なし| +|ノート追加後|該当ノートを選択する| +|グリッドをドラッグ|矩形選択| +|グリッドドラッグ後|選択中のノートの選択| +|Shift + グリッドをドラッグ|矩形選択| +|ノートをクリック|ノート単体選択| +|Ctrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートを選択する| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートを選択する| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| +### ペンツール +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|ノート追加| +|ノート選択中にグリッドをクリック|ノート追加| +|グリッドをダブルクリック|なし| +|ノート追加後|該当ノートを選択しない| +|グリッドをドラッグ|ノート追加| +|グリッドドラッグ後|選択中のノートを選択する| +|Shift + グリッドをドラッグ|なし| +|ノートをクリック|ノート単体選択| +|Ctrl or Command + ノートをクリック|ノート複数選択| +|ノートをダブルクリック|ノートの歌詞編集| +|ノートをドラッグ|ノート移動| +|ノートドラッグ後|該当ノートを選択する| +|ノートの端をドラッグ|ノートリサイズ| +|ノートリサイズ後|該当ノートを選択しない| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| +### 消しゴムツール +|元の状態と操作|結果| +|:--|:--| +|グリッドをクリック|なし| +|ノート選択中にグリッドをクリック|すべて選択解除| +|グリッドをダブルクリック|なし| +|ノート追加後|なし| +|グリッドをドラッグ|なし| +|グリッドドラッグ後|なし| +|Shift + グリッドをドラッグ|なし| +|ノートをクリック|該当ノートを削除| +|Ctrl or Command + ノートをクリック|なし| +|ノートをダブルクリック|なし| +|ノートをドラッグ|なし| +|ノートドラッグ後|なし| +|ノートの端をドラッグ|なし| +|ノートリサイズ後|なし| +|Escキー|すべて選択解除| +|ノートを選択中にDeleteキー|該当ノートの削除| + +*/ +// 編集モード仮置き(試行用) +const editModeTemp = ref("TOOLLESS_A"); + +// ツール選択の初期値を設定 +const selectedToolTemp = ref("SELECT_FIRST"); + +watch(editModeTemp, (newMode) => { + if (newMode === "TOOLSELECT_A") { + selectedToolTemp.value = "SELECT_FIRST"; + } else if (newMode === "TOOLSELECT_B") { + selectedToolTemp.value = "SELECTOR"; + } else { + selectedToolTemp.value = null; + } +}); + +watch([editModeTemp, selectedToolTemp], () => { + if (editModeTemp.value === "TOOLSELECT_A") { + if (selectedToolTemp.value === "SELECT_FIRST") { + setCursorState(CursorState.UNSET); + } else if (selectedToolTemp.value === "EDIT_FIRST") { + setCursorState(CursorState.CROSSHAIR); + } else { + setCursorState(CursorState.UNSET); + } + } else if (editModeTemp.value === "TOOLSELECT_B") { + if (selectedToolTemp.value === "SELECTOR") { + setCursorState(CursorState.CROSSHAIR); + } else if (selectedToolTemp.value === "PEN") { + setCursorState(CursorState.DRAW); + } else if (selectedToolTemp.value === "ERASER") { + setCursorState(CursorState.ERASE); + } else { + setCursorState(CursorState.UNSET); + } + } +}); + +// ノート追加後に選択するかどうか +const shouldSelectNoteAfterAdding = () => { + if (editModeTemp.value === "TOOLSELECT_A") { + return true; + } else if (editModeTemp.value === "TOOLSELECT_B") { + return true; + } + // 既存のTOOLLESSモードの処理 + return ["TOOLLESS_A", "TOOLLESS_D"].includes(editModeTemp.value); +}; + +// ノート編集後に選択するかどうか +const shouldSelectNoteAfterEditing = () => { + if (editModeTemp.value === "TOOLSELECT_A") { + return selectedToolTemp.value === "SELECT_FIRST"; + } else if (editModeTemp.value === "TOOLSELECT_B") { + return selectedToolTemp.value !== "PEN"; + } + // 既存のTOOLLESSモードの処理 + return ["TOOLLESS_A", "TOOLLESS_C", "TOOLLESS_D"].includes( + editModeTemp.value, + ); +}; + +// クリック時にノートを追加するかどうか +const shouldAddNoteOnClick = (event: MouseEvent) => { + if (editModeTemp.value === "TOOLSELECT_A") { + return ( + selectedToolTemp.value === "EDIT_FIRST" && + !isOnCommandOrCtrlKeyDown(event) + ); + } else if (editModeTemp.value === "TOOLSELECT_B") { + return selectedToolTemp.value === "PEN"; + } + return ["TOOLLESS_A", "TOOLLESS_B", "TOOLLESS_C"].includes( + editModeTemp.value, + ); +}; + +// ダブルクリック時にノートを追加するかどうか +const shouldAddNoteOnDoubleClick = () => { + if (editModeTemp.value === "TOOLSELECT_A") { + return selectedToolTemp.value === "SELECT_FIRST"; + } + return ["TOOLLESS_D"].includes(editModeTemp.value); +}; + +// Shiftキーが押されているときに矩形選択を開始するかどうか +const shouldStartRectSelectIfShiftKeyDown = () => { + if ( + editModeTemp.value === "TOOLSELECT_A" || + editModeTemp.value === "TOOLSELECT_B" + ) { + return selectedToolTemp.value !== "ERASER"; + } + return ["TOOLLESS_A", "TOOLLESS_B", "TOOLLESS_C"].includes( + editModeTemp.value, + ); +}; + +// ノート選択中にグリッドをクリック時に選択解除するかどうか +const shouldDeselectAllOnClick = (event: MouseEvent) => { + if (editModeTemp.value === "TOOLSELECT_A") { + return selectedToolTemp.value === "SELECT_FIRST"; + } else if (editModeTemp.value === "TOOLSELECT_B") { + return selectedToolTemp.value === "SELECTOR"; + } + if ( + editModeTemp.value === "TOOLLESS_B" || + editModeTemp.value === "TOOLLESS_C" + ) { + // ノート選択中にグリッドをクリックしたら選択解除 + return isNoteSelected.value && !isOnCommandOrCtrlKeyDown(event); + } else if (editModeTemp.value === "TOOLLESS_D") { + // 常に選択解除 + return true; + } else { + return false; + } +}; + +// ノート選択中にCtrl or Command + グリッドをクリック時に選択解除するかどうか +const shouldDeselectAllOnCtrlOrCommandClick = (event: MouseEvent) => { + if (editModeTemp.value === "TOOLLESS_A") { + return isNoteSelected.value && isOnCommandOrCtrlKeyDown(event); + } + return false; +}; + +// Ctrl or Command + ノートクリック時に複数選択するかどうか +const shouldSelectMultipleNotesOnCtrlOrCommandClick = () => { + if ( + editModeTemp.value === "TOOLSELECT_A" || + editModeTemp.value === "TOOLSELECT_B" + ) { + return selectedToolTemp.value !== "ERASER"; + } + return ["TOOLLESS_A", "TOOLLESS_B", "TOOLLESS_C", "TOOLLESS_D"].includes( + editModeTemp.value, + ); +}; + +// Shiftキーを押さずにグリッドドラッグで矩形選択をするかどうか +const shouldRectSelectOnDrag = () => { + if (editModeTemp.value === "TOOLSELECT_A") { + return selectedToolTemp.value === "SELECT_FIRST"; + } else if (editModeTemp.value === "TOOLSELECT_B") { + return selectedToolTemp.value === "SELECTOR"; + } + return ["TOOLLESS_D"].includes(editModeTemp.value); +}; + +// ノートをダブルクリックで歌詞編集を行うかどうか +const shouldEditLyricOnDoubleClickNote = () => { + if ( + editModeTemp.value === "TOOLSELECT_A" || + editModeTemp.value === "TOOLSELECT_B" + ) { + return selectedToolTemp.value !== "ERASER"; + } + return ["TOOLLESS_A", "TOOLLESS_B", "TOOLLESS_C", "TOOLLESS_D"].includes( + editModeTemp.value, + ); +}; + +// ノートをクリックで削除するかどうか +const shouldDeleteNoteOnClick = () => { + return ( + editModeTemp.value === "TOOLSELECT_B" && selectedToolTemp.value === "ERASER" + ); +}; + // 選択中のトラックID const selectedTrackId = computed(() => store.getters.SELECTED_TRACK_ID); @@ -837,13 +1255,17 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => { } } void store.dispatch("SELECT_NOTES", { noteIds: noteIdsToSelect }); - } else if (isOnCommandOrCtrlKeyDown(event)) { + } else if ( + isOnCommandOrCtrlKeyDown(event) && + shouldSelectMultipleNotesOnCtrlOrCommandClick() + ) { + // Ctrl または Command キーが押されている場合の複数選択 void store.dispatch("SELECT_NOTES", { noteIds: [note.id] }); } else if (!selectedNoteIds.value.has(note.id)) { void selectOnlyThis(note); } - for (const note of selectedNotes.value) { - copiedNotes.push({ ...note }); + for (const selectedNote of selectedNotes.value) { + copiedNotes.push({ ...selectedNote }); } } dragStartTicks = cursorTicks; @@ -896,25 +1318,37 @@ const endPreview = () => { if (previewStartEditTarget === "NOTE") { // 編集ターゲットがノートのときにプレビューを開始した場合の処理 if (edited) { + const previewTrackId = selectedTrackId.value; + if (previewMode.value === "ADD_NOTE") { + // ノートの追加処理 void store.dispatch("COMMAND_ADD_NOTES", { notes: previewNotes.value, - trackId: selectedTrackId.value, + trackId: previewTrackId, }); - void store.dispatch("SELECT_NOTES", { - noteIds: previewNotes.value.map((value) => value.id), - }); - } else { + if (shouldSelectNoteAfterAdding()) { + const noteIds = previewNotes.value.map((note) => note.id); + void store.dispatch("SELECT_NOTES", { noteIds }); + } else { + void store.dispatch("DESELECT_ALL_NOTES"); + } + } else if ( + previewMode.value === "MOVE_NOTE" || + previewMode.value === "RESIZE_NOTE_RIGHT" || + previewMode.value === "RESIZE_NOTE_LEFT" + ) { + // ノートの編集処理(移動・リサイズ) void store.dispatch("COMMAND_UPDATE_NOTES", { notes: previewNotes.value, - trackId: selectedTrackId.value, - }); - } - if (previewNotes.value.length === 1) { - void store.dispatch("PLAY_PREVIEW_SOUND", { - noteNumber: previewNotes.value[0].noteNumber, - duration: PREVIEW_SOUND_DURATION, + trackId: previewTrackId, }); + + if (shouldSelectNoteAfterEditing()) { + const noteIds = previewNotes.value.map((note) => note.id); + void store.dispatch("SELECT_NOTES", { noteIds }); + } else { + void store.dispatch("DESELECT_ALL_NOTES"); + } } } } else if (previewStartEditTarget === "PITCH") { @@ -925,7 +1359,7 @@ const endPreview = () => { } const previewPitchEditType = previewPitchEdit.value.type; if (previewPitchEditType === "draw") { - // カーソルを動かさずにマウスのボタンを離したときに1フレームのみの変更になり、 + // カーソルを動かさずにマウスのボタンを離したときに1フームのみの変更になり、 // 1フレームの変更はピッチ編集ラインとして表示されないので、無視する if (previewPitchEdit.value.data.length >= 2) { // 平滑化を行う @@ -954,27 +1388,36 @@ const endPreview = () => { throw new ExhaustiveError(previewStartEditTarget); } previewMode.value = "IDLE"; + previewNotes.value = []; + copiedNotesForPreview.clear(); + edited = false; + setCursorState(CursorState.UNSET); }; const onNoteBarMouseDown = (event: MouseEvent, note: Note) => { - if (event.button !== 0) { - return; - } - if (editTarget.value !== "NOTE") { + if (editTarget.value !== "NOTE" || !isSelfEventTarget(event)) { return; } - if (isOnCommandOrCtrlKeyDown(event)) { - if (selectedNoteIds.value.has(note.id)) { - // 選択済みのノートをCtrl+クリックした場合、選択解除する - void store.dispatch("DESELECT_NOTES", { noteIds: [note.id] }); - } else { - // 選択されていないノートをCtrl+クリックした場合、選択に追加する + + const mouseButton = getButton(event); + if (mouseButton === "LEFT_BUTTON") { + if ( + isOnCommandOrCtrlKeyDown(event) && + shouldSelectMultipleNotesOnCtrlOrCommandClick() + ) { + // CtrlまたはCommandキーが押されている場合、ノートを追加選択 void store.dispatch("SELECT_NOTES", { noteIds: [note.id] }); - } - } else { - if (!selectedNoteIds.value.has(note.id)) { + } else if (shouldDeleteNoteOnClick()) { + // ノート削除(消しゴムツール) + void store.dispatch("COMMAND_REMOVE_NOTES", { + noteIds: [note.id], + trackId: selectedTrackId.value, + }); + } else if (!selectedNoteIds.value.has(note.id)) { + // クリックしたノートが未選択の場合、そのノートのみを選択 void selectOnlyThis(note); } + // 選択状態の変更が完了した後でプレビューを開始 startPreview(event, "MOVE_NOTE", note); } }; @@ -983,9 +1426,11 @@ const onNoteBarDoubleClick = (event: MouseEvent, note: Note) => { if (editTarget.value !== "NOTE") { return; } - const mouseButton = getButton(event); - if (mouseButton === "LEFT_BUTTON" && note.id !== state.editingLyricNoteId) { - void store.dispatch("SET_EDITING_LYRIC_NOTE_ID", { noteId: note.id }); + if (shouldEditLyricOnDoubleClickNote()) { + const mouseButton = getButton(event); + if (mouseButton === "LEFT_BUTTON" && note.id !== state.editingLyricNoteId) { + void store.dispatch("SET_EDITING_LYRIC_NOTE_ID", { noteId: note.id }); + } } }; @@ -1018,33 +1463,36 @@ const onMouseDown = (event: MouseEvent) => { return; } const mouseButton = getButton(event); - // TODO: メニューが表示されている場合はメニュー非表示のみ行いたい if (editTarget.value === "NOTE") { if (mouseButton === "LEFT_BUTTON") { - if (event.shiftKey) { + if (editingLyricNote.value != undefined) { + return; + } + if (shouldStartRectSelectIfShiftKeyDown() && event.shiftKey) { + // Shiftキーが押されている場合の矩形選択 + isRectSelecting.value = true; + rectSelectStartX.value = cursorX.value; + rectSelectStartY.value = cursorY.value; + setCursorState(CursorState.CROSSHAIR); + } else if (shouldRectSelectOnDrag()) { + // シフトキーなしでの矩形選択(TOOLLESS_D) isRectSelecting.value = true; rectSelectStartX.value = cursorX.value; rectSelectStartY.value = cursorY.value; setCursorState(CursorState.CROSSHAIR); - } else if (selectedNoteIds.value.size > 0) { - // ノートが選択されている場合は、選択を解除する + } else if (shouldDeselectAllOnCtrlOrCommandClick(event)) { + // Ctrl or Command + グリッドをクリック時に選択解除 void store.dispatch("DESELECT_ALL_NOTES"); - } else { + } else if (shouldAddNoteOnClick(event)) { + // ノート追加 startPreview(event, "ADD_NOTE"); + } else if (shouldDeselectAllOnClick(event)) { + // 選択解除 + void store.dispatch("DESELECT_ALL_NOTES"); } } else { void store.dispatch("DESELECT_ALL_NOTES"); } - } else if (editTarget.value === "PITCH") { - if (mouseButton === "LEFT_BUTTON") { - if (isOnCommandOrCtrlKeyDown(event)) { - startPreview(event, "ERASE_PITCH"); - } else { - startPreview(event, "DRAW_PITCH"); - } - } - } else { - throw new ExhaustiveError(editTarget.value); } }; @@ -1068,14 +1516,6 @@ const onMouseMove = (event: MouseEvent) => { const guideLineBaseX = tickToBaseX(guideLineTicks, tpqn.value); guideLineX.value = guideLineBaseX * zoomX.value; } - // ノート選択状態に応じてカーソルを変更 - if (editTarget.value === "NOTE") { - if (selectedNoteIds.value.size > 0) { - setCursorState(CursorState.UNSET); - } else { - setCursorState(CursorState.DRAW); - } - } }; const onMouseUp = (event: MouseEvent) => { @@ -1090,6 +1530,51 @@ const onMouseUp = (event: MouseEvent) => { } }; +// ダブルクリックで追加(コピペ) +const onDoubleClick = (event: MouseEvent) => { + if (editTarget.value !== "NOTE") { + return; + } + if (shouldAddNoteOnDoubleClick()) { + const mouseButton = getButton(event); + if (mouseButton === "LEFT_BUTTON") { + const sequencerBodyElement = sequencerBody.value; + if (!sequencerBodyElement) { + throw new Error("sequencerBodyElement is null."); + } + cursorX.value = getXInBorderBox(event.clientX, sequencerBodyElement); + cursorY.value = getYInBorderBox(event.clientY, sequencerBodyElement); + + const cursorBaseX = (scrollX.value + cursorX.value) / zoomX.value; + const cursorBaseY = (scrollY.value + cursorY.value) / zoomY.value; + const cursorTicks = baseXToTick(cursorBaseX, tpqn.value); + const cursorNoteNumber = baseYToNoteNumber(cursorBaseY, true); + const guideLineTicks = + Math.round(cursorTicks / snapTicks.value - 0.25) * snapTicks.value; + + if (cursorNoteNumber < 0 || cursorNoteNumber >= keyInfos.length) { + return; + } + const note = { + id: NoteId(uuid4()), + position: guideLineTicks, + duration: snapTicks.value, + noteNumber: cursorNoteNumber, + lyric: getDoremiFromNoteNumber(cursorNoteNumber), + }; + void store.dispatch("COMMAND_ADD_NOTES", { + notes: [note], + trackId: selectedTrackId.value, + }); + void store.dispatch("SELECT_NOTES", { noteIds: [note.id] }); + void store.dispatch("PLAY_PREVIEW_SOUND", { + noteNumber: note.noteNumber, + duration: PREVIEW_SOUND_DURATION, + }); + } + } +}; + /** * 矩形選択。 * @param additive 追加選択とするかどうか。 @@ -1696,4 +2181,50 @@ const contextMenuData = computed(() => { color: var(--scheme-color-primary-fixed-dim); } } + +.edit-mode-selector { + background: var(--scheme-color-surface); + border: 1px solid var(--scheme-color-outline); + position: fixed; + top: 128px; + right: 24px; + z-index: 10; + display: flex; + align-items: center; + padding: 8px; + border-radius: 8px; +} + +.edit-mode-selector label { + margin-right: 8px; + color: var(--scheme-color-on-surface); +} + +.edit-mode-selector select { + padding: 4px; + background-color: var(--scheme-color-surface); + color: var(--scheme-color-on-surface); + border: 1px solid var(--scheme-color-outline-variant); + border-radius: 8px; +} + +.tool-palette { + display: flex; + flex-direction: column; + background: var(--scheme-color-surface); + border: 1px solid var(--scheme-color-outline); + position: fixed; + top: 136px; + left: 64px; + z-index: 10; + padding: 8px; + border-radius: 8px; + gap: 4px; + + .q-btn { + &.q-btn--active { + background-color: var(--scheme-color-primary-container); + } + } +}