Skip to content

Commit

Permalink
Add: 良い感じにたたむように
Browse files Browse the repository at this point in the history
  • Loading branch information
sevenc-nanashi committed Nov 3, 2024
1 parent 1aacc8e commit 3bc6e06
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/components/Sing/SequencerGrid/Presentation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ const snapLinePositions = (timeSignature: TimeSignature) => {
const snapCount = Math.floor(measureTicks / snapTicks);
return Array.from({ length: snapCount }, (_, index) => {
const currentTick = snapTicks * index;
const currentTick = snapTicks * (index + 1);
return Math.round(
(currentTick / measureTicks) * measureWidth(timeSignature),
);
Expand Down
115 changes: 106 additions & 9 deletions src/components/Sing/SequencerRuler/Presentation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
:data-ui-locked="uiLocked"
@click="onClick"
@contextmenu="onContextMenu"
@mousemove="updateHoveredTempoOrTimeSignatureChange"
@mouseleave="hoveredTempoOrTimeSignatureChange = null"
>
<slot
name="contextMenu"
Expand Down Expand Up @@ -64,7 +66,7 @@
v-for="measureInfo in measureInfos"
:key="measureInfo.number"
font-size="12"
:x="measureInfo.x - offset + 4"
:x="measureInfo.x - offset + textPadding"
y="34"
class="sequencer-ruler-measure-number"
>
Expand All @@ -76,8 +78,9 @@
:key="tempoOrTimeSignatureChange.position"
>
<text
:id="`tempo-or-time-signature-change-${tempoOrTimeSignatureChange.position}`"
font-size="12"
:x="tempoOrTimeSignatureChange.x - offset + 4"
:x="tempoOrTimeSignatureChange.x - offset + textPadding"
y="16"
class="sequencer-ruler-tempo-or-time-signature-change"
@click.stop="
Expand All @@ -93,7 +96,13 @@
)
"
>
{{ tempoOrTimeSignatureChange.text }}
{{
tempoOrTimeSignatureChange.displayType === "full"
? tempoOrTimeSignatureChange.text
: tempoOrTimeSignatureChange.displayType === "ellipsis"
? `...`
: "\u200b"
}}
</text>
<line
:x1="tempoOrTimeSignatureChange.x - offset"
Expand All @@ -104,6 +113,39 @@
/>
</template>
</svg>
<QTooltip
v-for="tempoOrTimeSignatureChange in collapsedTempoOrTimeSignatureChanges"
:key="tempoOrTimeSignatureChange.position"
:target="`#tempo-or-time-signature-change-${tempoOrTimeSignatureChange.position}`"
:modelValue="
hoveredTempoOrTimeSignatureChange ===
tempoOrTimeSignatureChange.position
"
@update:modelValue="
(value) =>
(hoveredTempoOrTimeSignatureChange = value
? tempoOrTimeSignatureChange.position
: null)
"
>
{{
tempoOrTimeSignatureChange.tempoChange
? `BPM:${tempoOrTimeSignatureChange.tempoChange.bpm}`
: ""
}}
<br
v-if="
tempoOrTimeSignatureChange.tempoChange &&
tempoOrTimeSignatureChange.timeSignatureChange
"
/>
{{
tempoOrTimeSignatureChange.timeSignatureChange
? `拍子:${tempoOrTimeSignatureChange.timeSignatureChange.beats}/${tempoOrTimeSignatureChange.timeSignatureChange.beatType}`
: ""
}}
</QTooltip>

<div class="sequencer-ruler-border-bottom"></div>
<div
class="sequencer-ruler-playhead"
Expand Down Expand Up @@ -141,6 +183,7 @@ import ContextMenu, {
import { UnreachableError } from "@/type/utility";
import TempoChangeDialog from "@/components/Dialog/TempoOrTimeSignatureChangeDialog/TempoChangeDialog.vue";
import TimeSignatureChangeDialog from "@/components/Dialog/TempoOrTimeSignatureChangeDialog/TimeSignatureChangeDialog.vue";
import { predictTextWidth } from "@/domain/dom";
const props = defineProps<{
offset: number;
Expand Down Expand Up @@ -316,9 +359,24 @@ const onContextMenu = async (event: MouseEvent) => {
type TempoOrTimeSignatureChange = {
position: number;
text: string;
tempoChange: Tempo | undefined;
timeSignatureChange: TimeSignature | undefined;
x: number;
displayType: "full" | "ellipsis" | "hidden";
};
const hoveredTempoOrTimeSignatureChange = ref<number | null>(null);
const updateHoveredTempoOrTimeSignatureChange = (event: MouseEvent) => {
const mouseX = props.offset + event.offsetX;
const tempoOrTimeSignatureChange = tempoOrTimeSignatureChanges.value.find(
(tempoOrTimeSignatureChange, i) =>
tempoOrTimeSignatureChange.displayType !== "full" &&
tempoOrTimeSignatureChange.x <= mouseX &&
mouseX <= (tempoOrTimeSignatureChanges.value.at(i + 1)?.x ?? Infinity),
);
hoveredTempoOrTimeSignatureChange.value =
tempoOrTimeSignatureChange?.position ?? null;
};
const onTempoOrTimeSignatureChangeClick = async (
event: MouseEvent,
tempoOrTimeSignatureChange: TempoOrTimeSignatureChange,
Expand All @@ -332,6 +390,7 @@ const currentMeasure = computed(() =>
tickToMeasureNumber(playheadTicks.value, props.timeSignatures, props.tpqn),
);
const textPadding = 4;
const tempoOrTimeSignatureChanges = computed<TempoOrTimeSignatureChange[]>(
() => {
const timeSignaturesWithTicks = tsPositions.value.map((tsPosition, i) => ({
Expand All @@ -347,11 +406,7 @@ const tempoOrTimeSignatureChanges = computed<TempoOrTimeSignatureChange[]>(
};
});
const result: {
position: number;
text: string;
x: number;
}[] = [
const tempoOrTimeSignatureChanges: TempoOrTimeSignatureChange[] = [
...Map.groupBy(
[...tempos, ...timeSignaturesWithTicks],
(item) => item.position,
Expand All @@ -372,13 +427,51 @@ const tempoOrTimeSignatureChanges = computed<TempoOrTimeSignatureChange[]>(
return {
position: tick,
text: [tempoText, timeSignatureText].join(" "),
tempoChange: tempo,
timeSignatureChange: timeSignature,
x: tickToBaseX(tick, props.tpqn) * props.sequencerZoomX,
displayType: "full" as const,
};
});
return result;
const collapsedTextWidth =
predictTextWidth("...", {
fontSize: 12,
fontFamily: "Unhinted Rounded M+ 1p",
fontWeight: "700",
}) +
textPadding * 2;
for (const [
i,
tempoOrTimeSignatureChange,
] of tempoOrTimeSignatureChanges.entries()) {
const next = tempoOrTimeSignatureChanges.at(i + 1);
if (!next) {
continue;
}
const requiredWidth =
predictTextWidth(tempoOrTimeSignatureChange.text, {
fontSize: 12,
fontFamily: "Unhinted Rounded M+ 1p",
fontWeight: "700",
}) + textPadding;
const width = next.x - tempoOrTimeSignatureChange.x;
if (collapsedTextWidth > width) {
tempoOrTimeSignatureChange.displayType = "hidden";
} else if (requiredWidth > width) {
tempoOrTimeSignatureChange.displayType = "ellipsis";
}
}
return tempoOrTimeSignatureChanges;
},
);
const collapsedTempoOrTimeSignatureChanges = computed(() =>
tempoOrTimeSignatureChanges.value.filter(
(tempoOrTimeSignatureChange) =>
tempoOrTimeSignatureChange.displayType !== "full",
),
);
const currentTempo = computed(() => {
const maybeTempo = props.tempos.findLast((tempo) => {
Expand Down Expand Up @@ -559,6 +652,10 @@ const contextMenudata = computed<ContextMenuItemData[]>(
stroke-width: 1px;
}
.sequencer-ruler-tempo-or-time-signature-change-hitbox {
pointer-events: all;
}
.sequencer-ruler-measure-line {
backface-visibility: hidden;
stroke: var(--scheme-color-sing-ruler-measure-line);
Expand Down
25 changes: 25 additions & 0 deletions src/domain/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,28 @@ export function setThemeToCss(theme: ThemeConf) {
export function setFontToCss(font: EditorFontType) {
document.body.setAttribute("data-editor-font", font);
}

let textWidthTempCanvas: HTMLCanvasElement | undefined;
let textWidthTempContext: CanvasRenderingContext2D | undefined;

export type FontSpecification = {
fontSize: number;
fontFamily: string;
fontWeight: string;
};
/**
* 特定のフォントでの文字列の描画幅を取得する。
* @see https://stackoverflow.com/a/21015393
*/
export function predictTextWidth(text: string, font: FontSpecification) {
if (!textWidthTempCanvas) {
textWidthTempCanvas = document.createElement("canvas");
textWidthTempContext = textWidthTempCanvas.getContext("2d") ?? undefined;
}
if (!textWidthTempContext) {
throw new Error("Failed to get 2d context");
}
textWidthTempContext.font = `${font.fontWeight} ${font.fontSize}px ${font.fontFamily}`;
const metrics = textWidthTempContext.measureText(text);
return metrics.width;
}

0 comments on commit 3bc6e06

Please sign in to comment.