This commit is contained in:
2024-06-01 20:16:48 +02:00
parent 80fa033a88
commit 87d53591f6
8 changed files with 97 additions and 108 deletions

View File

@@ -44,6 +44,7 @@ export class Beat extends EffectScoped {
barSettingsLocked: Ref<boolean>; barSettingsLocked: Ref<boolean>;
name: Ref<string>; name: Ref<string>;
saveDirty = ref(false); saveDirty = ref(false);
displaySticking = ref(false);
constructor(opts: BeatGroupInitOptions) { constructor(opts: BeatGroupInitOptions) {
super(); super();
@@ -112,19 +113,13 @@ export class Beat extends EffectScoped {
} }
} }
getTrackByIndex(trackIndex: number) {
const track = this.tracks.value[trackIndex];
if (!track) {
throw new Error(`Could not find the track with index: ${trackIndex}`);
}
return track;
}
insertAt(trackIndex: number, newIndex: number): void { insertAt(trackIndex: number, newIndex: number): void {
const track = this.getTrackByIndex(trackIndex); const track = this.tracks.value[trackIndex];
this.tracks.value.splice(trackIndex, 1); if (track) {
this.tracks.value = this.tracks.value.slice(0, newIndex).concat([track]).concat(this.tracks.value.slice(newIndex)); this.tracks.value.splice(trackIndex, 1);
triggerRef(this.tracks); this.tracks.value = this.tracks.value.slice(0, newIndex).concat([track]).concat(this.tracks.value.slice(newIndex));
triggerRef(this.tracks);
}
} }
notifyChange() { notifyChange() {
@@ -153,10 +148,12 @@ export class Beat extends EffectScoped {
} }
removeTrack(index: number): void { removeTrack(index: number): void {
const track = this.getTrackByIndex(index); const track = this.tracks.value[index];
this.tracks.value.splice(index, 1); if (track) {
track.destroy(); this.tracks.value.splice(index, 1);
triggerRef(this.tracks); track.destroy();
triggerRef(this.tracks);
}
} }
bakeLoops(): void { bakeLoops(): void {
@@ -166,6 +163,10 @@ export class Beat extends EffectScoped {
this.tracks.value.forEach(track => track.bakeLoops()); this.tracks.value.forEach(track => track.bakeLoops());
} }
getTracks(): Track[] {
return this.tracks.value;
}
serialise(): Readonly<BeatSerial> { serialise(): Readonly<BeatSerial> {
return { return {
tracks: this.tracks.value.map(track => track.serialise()), tracks: this.tracks.value.map(track => track.serialise()),

View File

@@ -34,10 +34,7 @@ export type TrackSerial = z.infer<typeof TrackSerialSchema>;
export const TrackUnitTypeList = [ "Normal", "GhostNote", "Accent", "GhostNoteAccent" ] as const; export const TrackUnitTypeList = [ "Normal", "GhostNote", "Accent", "GhostNoteAccent" ] as const;
export type TrackUnitType = typeof TrackUnitTypeList[number]; export type TrackUnitType = typeof TrackUnitTypeList[number];
export const PaintableTrackUnitStickingTypeList = [ "lh", "rh", "lf", "rf" ] as const; export const TrackUnitStickingTypeList = [ "none", "lh", "rh", "lf", "rf" ] as const;
export type PaintableTrackUnitStickingType = typeof PaintableTrackUnitStickingTypeList[number];
export const TrackUnitStickingTypeList = [ "lh", "rh", "lf", "rf" ] as const;
export type TrackUnitStickingType = typeof TrackUnitStickingTypeList[number]; export type TrackUnitStickingType = typeof TrackUnitStickingTypeList[number];
export function isValidTimeSigRange(sig: number): boolean { export function isValidTimeSigRange(sig: number): boolean {
@@ -53,7 +50,7 @@ export function deserialise(serial: unknown, manager: BeatManager) {
const units = beat.units.isOn.map((isOn, i) => ({ const units = beat.units.isOn.map((isOn, i) => ({
on: isOn, on: isOn,
type: beat.units.type[i] ?? 0, type: beat.units.type[i] ?? 0,
stickingType: beat.units.stickingType[i] ?? 0, stickingType: beat.units.stickingType[i] ?? null,
})); }));
return Track.asScoped({ return Track.asScoped({
manager, manager,
@@ -79,10 +76,10 @@ export class Track extends EffectScoped {
private loopLengthInternal: any; private loopLengthInternal: any;
private timeSig: { up: number, down: number }; private timeSig: { up: number, down: number };
private manager: BeatManager; private manager: BeatManager;
private unitRecord: ShallowRef<TrackUnit[]>;
name: Ref<string>; name: Ref<string>;
timeSigUp: Ref<number>; timeSigUp: Ref<number>;
timeSigDown: Ref<number>; timeSigDown: Ref<number>;
unitRecord: ShallowRef<TrackUnit[]>;
barCount: Ref<number>; barCount: Ref<number>;
loopLength: Ref<number>; loopLength: Ref<number>;
looping: Ref<boolean>; looping: Ref<boolean>;
@@ -140,6 +137,10 @@ export class Track extends EffectScoped {
return this.unitRecord.value[index] ?? null; return this.unitRecord.value[index] ?? null;
} }
unitCount() {
return this.unitRecord.value.length;
}
private updateTrackUnitLength() { private updateTrackUnitLength() {
const newBarCount = this.barCount.value * this.timeSigUp.value; const newBarCount = this.barCount.value * this.timeSigUp.value;
if (newBarCount < this.unitRecord.value.length) { if (newBarCount < this.unitRecord.value.length) {
@@ -187,7 +188,7 @@ export class Track extends EffectScoped {
return { return {
on: false, on: false,
type: 0, type: 0,
stickingType: 0, stickingType: null,
}; };
} }

15
src/globals.d.ts vendored
View File

@@ -6,4 +6,19 @@ declare global {
} }
} }
declare module "*.gif" {
const value: string;
export = value;
}
declare module "*.png" {
const value: string;
export = value;
}
declare module "*.svg" {
const value: string;
export = value;
}
export {}; export {};

View File

@@ -3,7 +3,18 @@
<editable-text-field node-type="h3" class="beat-title" v-model="beat.name.value" /> <editable-text-field node-type="h3" class="beat-title" v-model="beat.name.value" />
<div class="beat-main-container"> <div class="beat-main-container">
<div class="beat-track-container" :class="{ dragging }"> <div class="beat-track-container" :class="{ dragging }">
<draggable @start="dragging = true" <template v-if="beat.displaySticking.value">
<div v-for="(_, type) in TrackUnitStickingTypeList.slice(1)"
class="beat-line">
<div class="track-name">{{ TrackUnitStickingTypeList[type + 1]?.toUpperCase() }}</div>
<span class="handle"><icon color="var(--color-ui-neutral-dark)" icon-name="list" /></span>
<sticking-track-view
:beat-index="beatIndex"
:sticking-type="type + 1" />
</div>
</template>
<draggable v-else
@start="dragging = true"
@end="dragging = false" @end="dragging = false"
animation="150" animation="150"
ghost-class="ghost" ghost-class="ghost"
@@ -27,11 +38,12 @@
<script setup lang="ts"> <script setup lang="ts">
import TrackView from "@/ui/Track/Track.vue"; import TrackView from "@/ui/Track/Track.vue";
import StickingTrackView from "@/ui/Track/StickingTrack.vue";
import EditableTextField from "@/ui/Widgets/EditableTextField/EditableTextField.vue"; import EditableTextField from "@/ui/Widgets/EditableTextField/EditableTextField.vue";
import { useBeatStore } from '@/BeatStore'; import { useBeatStore } from '@/BeatStore';
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import Draggable from "vuedraggable"; import Draggable from "vuedraggable";
import type { Track } from "@/Track"; import { TrackUnitStickingTypeList, type Track } from "@/Track";
import Icon from "@/ui/Widgets/Icon/Icon.vue"; import Icon from "@/ui/Widgets/Icon/Icon.vue";
const props = defineProps<{ const props = defineProps<{
@@ -43,7 +55,7 @@
const beat = computed(() => beats.value[props.beatIndex] ?? null); const beat = computed(() => beats.value[props.beatIndex] ?? null);
const tracks = computed(() => { const tracks = computed(() => {
const trackList = beat.value?.tracks.value ?? []; const trackList = beat.value?.getTracks() ?? [];
const length = trackList.length; const length = trackList.length;
const list: { index: number, track: Track }[] = []; const list: { index: number, track: Track }[] = [];
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {

View File

@@ -7,6 +7,9 @@
<div class="beat-settings-bar-count beat-settings-option"> <div class="beat-settings-bar-count beat-settings-option">
<number-input label="Boxes per bar: " v-model="beat.timeSigUp.value" /> <number-input label="Boxes per bar: " v-model="beat.timeSigUp.value" />
</div> </div>
<div class="beat-settings-bar-count beat-settings-option">
<bool-box label="Sticking mode: " v-model="beat.displaySticking.value" />
</div>
<div class="beat-settings-bar-count beat-settings-option"> <div class="beat-settings-bar-count beat-settings-option">
<bool-box label="Auto beat length: " v-model="beat.useAutoBeatLength.value" /> <bool-box label="Auto beat length: " v-model="beat.useAutoBeatLength.value" />
</div> </div>

View File

@@ -7,7 +7,6 @@
@click="selectedTool = 'track-unit-type'"> @click="selectedTool = 'track-unit-type'">
<track-unit-box <track-unit-box
v-if="activeTrackUnitType !== null" v-if="activeTrackUnitType !== null"
class="paint-button"
:sticking-type="null" :on="true" :type="activeTrackUnitType" /> :sticking-type="null" :on="true" :type="activeTrackUnitType" />
<icon v-else class="paint-button" icon-name="eraser" /> <icon v-else class="paint-button" icon-name="eraser" />
</div> </div>
@@ -17,7 +16,7 @@
:key="type ?? 'eraser'" :key="type ?? 'eraser'"
class="toolbox-button track-unit-type" class="toolbox-button track-unit-type"
:class="{ active: i === activeTrackUnitType || type === null && activeTrackUnitType === null }" :class="{ active: i === activeTrackUnitType || type === null && activeTrackUnitType === null }"
@click="activeTrackUnitType = type === null ? null : i; selectedTool = 'track-unit-type'"> @click="activeTrackUnitType = (type === null ? null : i); selectedTool = 'track-unit-type'">
<track-unit-box v-if="type !== null" :sticking-type="null" :on="true" :type="i" /> <track-unit-box v-if="type !== null" :sticking-type="null" :on="true" :type="i" />
<icon v-else icon-name="eraser" /> <icon v-else icon-name="eraser" />
</div> </div>
@@ -27,16 +26,16 @@
<dropdown> <dropdown>
<icon <icon
class="toolbox-button" class="toolbox-button"
:icon-name="activeStickingType ? (PaintableTrackUnitStickingTypeList[activeStickingType] ?? 'eraser') : 'eraser'" :icon-name="activeStickingType ? StickingTypeIconMap[TrackUnitStickingTypeList[activeStickingType]!] : 'eraser'"
:class="{ active: selectedTool === 'sticking' }" :class="{ active: selectedTool === 'sticking' }"
@click="selectedTool = 'sticking'" /> @click="selectedTool = 'sticking'" />
<template #content> <template #content>
<div class="details"> <div class="details">
<div v-for="(stickingType, i) in PaintableTrackUnitStickingTypeList" <div v-for="(stickingType, i) in TrackUnitStickingTypeList.slice(1)"
:key="stickingType" :key="stickingType ?? 'none'"
class="toolbox-button" class="toolbox-button"
:class="{ active: i === activeStickingType }" :class="{ active: i + 1 === activeStickingType }"
@click="activeStickingType = i; selectedTool = 'sticking'"> @click="activeStickingType = i + 1; selectedTool = 'sticking'">
<icon :icon-name="StickingTypeIconMap[stickingType]" /> <icon :icon-name="StickingTypeIconMap[stickingType]" />
</div> </div>
<div <div
@@ -54,7 +53,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAppStateStore } from "@/AppState"; import { useAppStateStore } from "@/AppState";
import { PaintableTrackUnitStickingTypeList, TrackUnitTypeList } from "@/Track"; import { TrackUnitStickingTypeList, TrackUnitTypeList } from "@/Track";
import { StickingTypeIconMap } from "@/ui/TrackUnit/trackUnit"; import { StickingTypeIconMap } from "@/ui/TrackUnit/trackUnit";
import Icon from "@/ui/Widgets/Icon/Icon.vue"; import Icon from "@/ui/Widgets/Icon/Icon.vue";
import Dropdown from '@/ui/Widgets/Dropdown/Dropdown.vue'; import Dropdown from '@/ui/Widgets/Dropdown/Dropdown.vue';

View File

@@ -1,32 +1,42 @@
<template> <template>
<div class="track"> <track-container>
<div class="track-main"> <template v-if="!locked">
<div class="track-unit-block"> <track-unit v-for="(trackUnit, i) in trackUnits"
<track-unit v-for="(trackUnit, i) in trackUnits" :key="`tu${ trackIndex }${ i }`"
:key="`tu${ trackIndex }${ i }`" :id="`tu${ trackIndex }${ i }`"
:id="`tu${ trackIndex }${ i }`" class="track-unit"
class="track-unit" :class="{ spaced: (i + 1) % beat!.timeSigUp.value === 0 }"
:class="{ spaced: (i + 1) % beat!.timeSigUp.value === 0 }" :sticking-type="trackUnit.stickingType"
:sticking-type="trackUnit.stickingType" :type="trackUnit.type"
:type="trackUnit.type" :on="trackUnit.on"
:on="trackUnit.on" @rotate-type="rotateTrackUnit(i)"
@rotate-type="rotateTrackUnit(i)" @deactivate="deactivateUnit(i)"
@deactivate="deactivateUnit(i)" @toggle="toggle(i)"
@toggle="toggle(i)" @apply-tool="applyCurrentToolToTrackUnit(i)" />
@apply-tool="applyCurrentToolToTrackUnit(i)" /> </template>
</div> <template v-else>
</div> <track-unit v-for="(trackUnit, i) in trackUnits"
</div> :key="`tu${ trackIndex }${ i }`"
:id="`tu${ trackIndex }${ i }`"
class="track-unit"
:class="{ spaced: (i + 1) % beat!.timeSigUp.value === 0 }"
:sticking-type="trackUnit.stickingType"
:type="trackUnit.type"
:on="trackUnit.on" />
</template>
</track-container>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import TrackUnit from "@/ui/TrackUnit/TrackUnit.vue"; import TrackUnit from "@/ui/TrackUnit/TrackUnit.vue";
import TrackContainer from "@/ui/Track/TrackContainer.vue";
import { useBeatStore } from "@/BeatStore"; import { useBeatStore } from "@/BeatStore";
import { computed } from 'vue'; import { computed } from 'vue';
import { useAppStateStore } from "@/AppState"; import { useAppStateStore } from "@/AppState";
const props = defineProps<{ const props = defineProps<{
beatIndex: number, beatIndex: number,
locked?: boolean,
trackIndex: number, trackIndex: number,
}>(); }>();
@@ -35,6 +45,7 @@
activeStickingType, activeStickingType,
activeTrackUnitType, activeTrackUnitType,
} = useAppStateStore(); } = useAppStateStore();
const { beats } = useBeatStore(); const { beats } = useBeatStore();
const beat = computed(() => beats.value[props.beatIndex] ?? null); const beat = computed(() => beats.value[props.beatIndex] ?? null);
const track = computed(() => beat.value?.tracks.value[props.trackIndex] ?? null); const track = computed(() => beat.value?.tracks.value[props.trackIndex] ?? null);
@@ -42,7 +53,7 @@
const trackUnits = computed(() => { const trackUnits = computed(() => {
const units = []; const units = [];
if (track.value) { if (track.value) {
for (let i = 0; i < track.value.unitRecord.value.length; i++) { for (let i = 0; i < track.value.unitCount(); i++) {
const unit = track.value.getUnitByIndex(i); const unit = track.value.getUnitByIndex(i);
if (unit) { if (unit) {
units.push(unit); units.push(unit);
@@ -90,61 +101,7 @@
margin-right: 1em; margin-right: 1em;
} }
} }
.track-unit-block {
height: 2em;
}
.track-spacer {
display: inline-block;
width: 1em;
height: 2em;
}
.track-main {
height: 36px;
}
.track-settings-container {
display: flex;
}
.track {
width: max-content;
& > * {
padding-right: 1em;
padding-left: 1em;
}
}
.vertical-mode { .vertical-mode {
.track-spacer {
display: block;
width: 2em;
height: 1em;
}
.track-unit-block {
height: auto;
width: 2em;
}
.track-main {
width: 2em;
margin-right: 4px;
display: block;
}
.track {
display: inline-block;
height: 36px;
& > * {
padding-right: 0;
padding-left: 0;
}
}
.track-unit { .track-unit {
&.spaced { &.spaced {
margin-bottom: 1em; margin-bottom: 1em;

View File

@@ -4,6 +4,7 @@ import type { IconName } from "@/ui/Widgets/Icon/icons";
export const TypeClasses = [ "Ghost", "Accent" ] as const; export const TypeClasses = [ "Ghost", "Accent" ] as const;
export const StickingTypeIconMap = { export const StickingTypeIconMap = {
none: null,
lf: 'lf', lf: 'lf',
lh: 'lh', lh: 'lh',
rf: 'rf', rf: 'rf',
@@ -17,7 +18,7 @@ export const TrackUnitTypeClassMap = {
"GhostNoteAccent": ["Ghost", "Accent"], "GhostNoteAccent": ["Ghost", "Accent"],
} as const satisfies Record<TrackUnitType, Readonly<string[]>>; } as const satisfies Record<TrackUnitType, Readonly<string[]>>;
export function getClasses(options: { on: boolean, stickingType: TrackUnitStickingType | null, type: TrackUnitType, highlightable?: boolean }) { export function getClasses(options: { on: boolean, stickingType: TrackUnitStickingType, type: TrackUnitType, highlightable?: boolean }) {
const classes = ["track-unit"]; const classes = ["track-unit"];
if (options.on) { if (options.on) {
classes.push("on"); classes.push("on");