diff --git a/src/components/Sing/SequencerLoopControl.vue b/src/components/Sing/SequencerLoopControl.vue
index e79ea6d1db..335563633e 100644
--- a/src/components/Sing/SequencerLoopControl.vue
+++ b/src/components/Sing/SequencerLoopControl.vue
@@ -2,12 +2,13 @@
@@ -83,6 +87,9 @@ import { useLoopControl } from "@/composables/useLoopControl";
import { useCursorState, CursorState } from "@/composables/useCursorState";
import { tickToBaseX, baseXToTick } from "@/sing/viewHelper";
import { getNoteDuration } from "@/sing/domain";
+import ContextMenu, {
+ ContextMenuItemData,
+} from "@/components/Menu/ContextMenu.vue";
const props = defineProps<{
width: number;
@@ -133,10 +140,10 @@ const adjustedHeight = computed(() =>
);
const onLoopAreaMouseDown = (event: MouseEvent) => {
+ if (event.button !== 0 || (event.ctrlKey && event.button === 0)) return;
if (isDragging.value) {
void stopDragging();
}
- if (event.button !== 0) return;
const target = event.currentTarget as HTMLElement;
const rect = target.getBoundingClientRect();
const x = event.clientX - rect.left + props.offset;
@@ -163,8 +170,14 @@ const onEndHandleMouseDown = (event: MouseEvent) => {
startDragging("end", event);
};
+const onHandleDoubleClick = () => {
+ // ハンドルのダブルクリックでループを0地点に設定する
+ void setLoopRange(0, 0);
+};
+
// ドラッグ開始処理
const startDragging = (target: "start" | "end", event: MouseEvent) => {
+ if (event.button !== 0) return;
isDragging.value = true;
dragTarget.value = target;
dragStartX.value = event.clientX;
@@ -178,6 +191,7 @@ const startDragging = (target: "start" | "end", event: MouseEvent) => {
// ドラッグ中処理
const onDrag = (event: MouseEvent) => {
if (!isDragging.value || !dragTarget.value) return;
+ if (event.button !== 0) return;
// ドラッグ中のX座標
const dx = event.clientX - dragStartX.value;
@@ -241,6 +255,34 @@ const stopDragging = async () => {
window.removeEventListener("mouseup", stopDragging);
};
+const onContextMenu = (event: MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+};
+const contextMenu = ref>();
+const contextMenuData = computed(() => {
+ return [
+ {
+ type: "button",
+ label: isLoopEnabled.value ? "ループ無効" : "ループ有効",
+ onClick: () => {
+ contextMenu.value?.hide();
+ void setLoopEnabled(!isLoopEnabled.value);
+ },
+ disableWhenUiLocked: true,
+ },
+ {
+ type: "button",
+ label: "ループを0地点にリセット",
+ onClick: () => {
+ contextMenu.value?.hide();
+ void setLoopRange(0, 0);
+ },
+ disableWhenUiLocked: true,
+ },
+ ];
+});
+
onUnmounted(() => {
setCursorState(CursorState.UNSET);
});
@@ -253,52 +295,78 @@ onUnmounted(() => {
left: 0;
width: 100%;
pointer-events: auto;
+ cursor: pointer;
- &:not(.cursor-ew-resize) {
- cursor: pointer;
+ &.cursor-ew-resize {
+ cursor: ew-resize;
}
- &.loop-dragging .loop-range-visible {
- fill: var(--scheme-color-outline-variant);
+ // ホバー時のループエリア
+ &:hover .loop-area {
+ fill: var(--scheme-color-sing-loop-area);
}
}
+// ループエリア
.loop-area {
fill: transparent;
+ transition: fill 0.1s ease-out;
}
+// ループ範囲
.loop-range {
- fill: transparent;
-}
-
-.loop-range-visible {
- fill: var(--scheme-color-primary-fixed-dim);
-}
+ &-area {
+ fill: transparent;
+ }
-.loop-disabled .loop-range-visible {
fill: var(--scheme-color-outline);
+ opacity: 1;
}
+// ループハンドル
.loop-handle {
- fill: var(--scheme-color-primary-fixed-dim);
- stroke: var(--scheme-color-primary-fixed-dim);
- stroke-width: 2;
+ fill: var(--scheme-color-outline);
+ stroke: var(--scheme-color-outline);
+ stroke-width: 0;
stroke-linejoin: round;
- &.loop-handle-disabled {
- fill: var(--scheme-color-secondary);
- stroke: var(--scheme-color-secondary);
+ &-no-length {
+ opacity: 0.5;
}
}
-.loop-disabled .loop-handle {
- fill: var(--scheme-color-outline);
- stroke: var(--scheme-color-outline);
-}
-
+// ドラッグエリア
.loop-drag-area {
fill: transparent;
cursor: ew-resize;
pointer-events: all;
}
+
+// ドラッグ中の状態
+.loop-dragging {
+ .loop-area {
+ fill: var(--scheme-color-sing-loop-area);
+ }
+
+ .loop-range {
+ opacity: 0.6;
+ }
+}
+
+// ループが有効な状態
+.loop-enabled {
+ .loop-range {
+ fill: var(--scheme-color-primary-fixed-dim);
+ }
+
+ .loop-handle {
+ fill: var(--scheme-color-primary-fixed-dim);
+ stroke: var(--scheme-color-primary-fixed-dim);
+
+ &-no-length {
+ fill: var(--scheme-color-outline);
+ stroke: var(--scheme-color-outline);
+ }
+ }
+}
diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue
index b2be429aea..ae0ef298e9 100644
--- a/src/components/Sing/ToolBar/ToolBar.vue
+++ b/src/components/Sing/ToolBar/ToolBar.vue
@@ -438,7 +438,7 @@ const goToZero = () => {
const { isLoopEnabled, setLoopEnabled } = useLoopControl();
const toggleLoop = () => {
- setLoopEnabled(!isLoopEnabled.value);
+ void setLoopEnabled(!isLoopEnabled.value);
};
const volume = computed({
diff --git a/src/styles/v2/sing-colors.scss b/src/styles/v2/sing-colors.scss
index 96c4189d30..ab741b62d8 100644
--- a/src/styles/v2/sing-colors.scss
+++ b/src/styles/v2/sing-colors.scss
@@ -408,6 +408,10 @@ SASSのmapなどで構造化+mixin一括処理などで処理可能ですが、
--scheme-color-sing-shadow-note: oklch(
var(--lr-84) var(--neutral-variant-c) var(--neutral-variant-h)
);
+
+ --scheme-color-sing-loop-area: oklch(
+ var(--lr-86) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
}
/* ダークテーマ */
@@ -671,4 +675,8 @@ SASSのmapなどで構造化+mixin一括処理などで処理可能ですが、
--scheme-color-sing-shadow-note: oklch(
var(--lr-40) var(--neutral-variant-c) var(--neutral-variant-h)
);
+
+ --scheme-color-sing-loop-area: oklch(
+ var(--lr-34) var(--neutral-variant-c) var(--neutral-variant-h)
+ );
}