update
This commit is contained in:
@@ -216,7 +216,6 @@
|
||||
}
|
||||
|
||||
.toolbox {
|
||||
flex: 1;
|
||||
width: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,61 @@
|
||||
<template>
|
||||
<div class="toolbox">
|
||||
<div class="main-row">
|
||||
<icon
|
||||
class="toolbox-button"
|
||||
icon-name="lh"
|
||||
:class="{ active: selectedTool === 'track-unit-type' }"
|
||||
@click="selectedTool = 'track-unit-type'" />
|
||||
<icon
|
||||
class="toolbox-button"
|
||||
icon-name="lf"
|
||||
:class="{ active: selectedTool === 'sticking' }"
|
||||
@click="selectedTool = 'sticking'" />
|
||||
<icon
|
||||
class="toolbox-button"
|
||||
:class="{ active: selectedTool === 'eraser' }"
|
||||
@click="selectedTool = 'eraser'" />
|
||||
<dropdown>
|
||||
<div class="toolbox-button paint-button-cont"
|
||||
:class="{ active: selectedTool === 'track-unit-type' }"
|
||||
@click="selectedTool = 'track-unit-type'">
|
||||
<div
|
||||
class="paint-button"
|
||||
:class="getClasses({ on: true, type: TrackUnitTypeList[activeTrackUnitType]!, stickingType: null })" />
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="details">
|
||||
<div v-for="(type, i) in TrackUnitTypeList"
|
||||
:key="type"
|
||||
class="toolbox-button"
|
||||
:class="{ active: i === activeTrackUnitType }"
|
||||
@click="activeTrackUnitType = i; selectedTool = 'track-unit-type'">
|
||||
<div :class="getClasses({ on: true, type, stickingType: null })" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
<dropdown>
|
||||
<icon
|
||||
class="toolbox-button"
|
||||
:icon-name="PaintableTrackUnitStickingTypeList[activeStickingType]!"
|
||||
:class="{ active: selectedTool === 'sticking' }"
|
||||
@click="selectedTool = 'sticking'" />
|
||||
<template #content>
|
||||
<div class="details">
|
||||
<div v-for="(stickingType, i) in PaintableTrackUnitStickingTypeList"
|
||||
:key="stickingType"
|
||||
class="toolbox-button"
|
||||
:class="{ active: i === activeStickingType }"
|
||||
@click="activeStickingType = i; selectedTool = 'sticking'">
|
||||
<icon :icon-name="StickingTypeIconMap[stickingType]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
<dropdown>
|
||||
<icon
|
||||
class="toolbox-button"
|
||||
icon-name="eraser"
|
||||
:class="{ active: selectedTool === 'eraser' }"
|
||||
@click="selectedTool = 'eraser'" />
|
||||
</dropdown>
|
||||
</div>
|
||||
<div v-if="selectedTool === 'track-unit-type'" class="details">
|
||||
<div v-for="(type, i) in TrackUnitTypeList"
|
||||
:key="type"
|
||||
class="toolbox-button"
|
||||
:class="{ active: i === activeTrackUnitType }"
|
||||
@click="activeTrackUnitType = i">
|
||||
<div :class="getClasses({ on: true, type, stickingType: 'none' })" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="selectedTool === 'sticking'" class="details">
|
||||
<div v-for="(stickingType, i) in TrackUnitStickingTypeList.slice(1)"
|
||||
:key="stickingType"
|
||||
class="toolbox-button"
|
||||
:class="{ active: i + 1 === activeStickingType }"
|
||||
@click="activeStickingType = i + 1">
|
||||
<icon :icon-name="StickingTypeIconMap[stickingType]" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="selectedTool === 'eraser'" class="details hidden" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStateStore } from "@/AppState";
|
||||
import { TrackUnitStickingTypeList, TrackUnitTypeList } from "@/Track";
|
||||
import { PaintableTrackUnitStickingTypeList, TrackUnitTypeList } from "@/Track";
|
||||
import { StickingTypeIconMap } from "@/ui/TrackUnit/trackUnit";
|
||||
import Icon from "@/ui/Widgets/Icon/Icon.vue";
|
||||
import Dropdown from '@/ui/Widgets/Dropdown/Dropdown.vue';
|
||||
import { getClasses } from "@/ui/TrackUnit/trackUnit";
|
||||
|
||||
const {
|
||||
@@ -53,41 +66,60 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.details {
|
||||
margin: auto;
|
||||
padding: 0.5em;
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
}
|
||||
|
||||
.toolbox {
|
||||
.main-row {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.details {
|
||||
margin: auto;
|
||||
height: 4em;
|
||||
width: min-content;
|
||||
border-radius: 0 0 1em 1em;
|
||||
padding: 0.5em;
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
&.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.track-unit {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
padding: 0.5em;
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
|
||||
&.paint-button-cont {
|
||||
padding: 0.25em;
|
||||
background-color: var(--color-ui-bg-dark);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-ui-bg-dark);
|
||||
|
||||
.paint-button {
|
||||
background-color: var(--color-ui-neutral-dark-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-ui-bg-dark);
|
||||
|
||||
.paint-button {
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
}
|
||||
}
|
||||
|
||||
.paint-button {
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
background-color: black;
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-ui-neutral-dark-hover);
|
||||
@@ -97,18 +129,19 @@
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
.toolbox-button {
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
.details {
|
||||
.toolbox-button {
|
||||
display: inline-block;
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-ui-neutral-dark-hover);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-ui-neutral-dark-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
@touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd"
|
||||
@contextmenu.prevent.stop="() => false">
|
||||
<icon :icon-name="iconName" />
|
||||
<icon v-if="iconName" :icon-name="iconName" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
|
||||
const {
|
||||
selectingUnits,
|
||||
lastTrackUnit,
|
||||
deselectingUnits,
|
||||
unitMouseStart,
|
||||
} = useAppStateStore();
|
||||
@@ -47,12 +46,19 @@
|
||||
|
||||
const classes = computed(() => getClasses({
|
||||
on: props.on,
|
||||
stickingType: TrackUnitStickingTypeList[props.stickingType] ?? 'none',
|
||||
stickingType: TrackUnitStickingTypeList[props.stickingType] ?? null,
|
||||
type: TrackUnitTypeList[props.type] ?? 'Normal',
|
||||
highlightable: true,
|
||||
}));
|
||||
|
||||
const iconName = computed(() => StickingTypeIconMap[TrackUnitStickingTypeList[props.stickingType] ?? 'none']);
|
||||
const iconName = computed(() => {
|
||||
const type = TrackUnitStickingTypeList[props.stickingType];
|
||||
if (type) {
|
||||
StickingTypeIconMap[type];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
function handleMouseDown(ev: MouseEvent): void {
|
||||
blockNextMouseUp = false;
|
||||
@@ -118,7 +124,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style scoped lang="scss">
|
||||
.track-unit {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
|
||||
@@ -4,26 +4,25 @@ import type { IconName } from "@/ui/Widgets/Icon/icons";
|
||||
export const TypeClasses = [ "Ghost", "Accent" ] as const;
|
||||
|
||||
export const StickingTypeIconMap = {
|
||||
none: null,
|
||||
lf: 'lf',
|
||||
lh: 'lh',
|
||||
rf: 'rf',
|
||||
rh: 'rh',
|
||||
} as const satisfies Readonly<Record<TrackUnitStickingType, IconName | null>>;
|
||||
} as const satisfies Record<TrackUnitStickingType, IconName | null>;
|
||||
|
||||
export const TrackUnitTypeClassMap = {
|
||||
"Normal": [],
|
||||
"GhostNote": ["Ghost"],
|
||||
"Accent": ["Accent"],
|
||||
"GhostNoteAccent": ["Ghost", "Accent"],
|
||||
} as const satisfies Readonly<Record<TrackUnitType, Readonly<string[]>>>;
|
||||
} as const satisfies Record<TrackUnitType, Readonly<string[]>>;
|
||||
|
||||
export function getClasses(options: { on: boolean, stickingType: TrackUnitStickingType, type: TrackUnitType, highlightable?: boolean }) {
|
||||
export function getClasses(options: { on: boolean, stickingType: TrackUnitStickingType | null, type: TrackUnitType, highlightable?: boolean }) {
|
||||
const classes = ["track-unit"];
|
||||
if (options.on) {
|
||||
classes.push("on");
|
||||
}
|
||||
if (StickingTypeIconMap[options.stickingType]) {
|
||||
if (options.stickingType && StickingTypeIconMap[options.stickingType]) {
|
||||
classes.push("icon-visible");
|
||||
}
|
||||
if (options.type) {
|
||||
|
||||
81
src/ui/Widgets/Dropdown/Dropdown.vue
Normal file
81
src/ui/Widgets/Dropdown/Dropdown.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div
|
||||
class="trigger"
|
||||
ref="trigger"
|
||||
@mouseenter="onMouseEnter"
|
||||
@touchstart="onMouseEnter"
|
||||
@mouseleave="onMouseLeave">
|
||||
<slot />
|
||||
</div>
|
||||
<Teleport to="#dropdowns">
|
||||
<div
|
||||
v-if="visible"
|
||||
ref="dropdown"
|
||||
class="dropdown"
|
||||
:class="{ visible }"
|
||||
@onmouseleave="visible = false">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const trigger = ref<HTMLDivElement>();
|
||||
const dropdown = ref<HTMLDivElement>();
|
||||
|
||||
const visible = ref(false);
|
||||
const top = ref('0px');
|
||||
const left = ref('0px');
|
||||
|
||||
function onMouseEnter(e: MouseEvent | TouchEvent) {
|
||||
visible.value = true;
|
||||
if (trigger.value) {
|
||||
const rect = trigger.value.getBoundingClientRect();
|
||||
top.value = rect.top + 'px';
|
||||
left.value = rect.width + rect.left + 'px';
|
||||
}
|
||||
window.addEventListener('touchstart', onWindowClick);
|
||||
window.addEventListener('click', onWindowClick);
|
||||
}
|
||||
|
||||
function outside(e: MouseEvent, el: HTMLElement) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return e.clientX < rect.x
|
||||
|| e.clientX > rect.x + rect.width
|
||||
|| e.clientY < rect.y
|
||||
|| e.clientY > rect.y + rect.height;
|
||||
}
|
||||
|
||||
function onWindowClick(e: MouseEvent | TouchEvent) {
|
||||
if (visible.value && e instanceof MouseEvent && dropdown.value && !outside(e, dropdown.value)) {
|
||||
visible.value = false;
|
||||
window.removeEventListener('touchstart', onWindowClick);
|
||||
window.removeEventListener('click', onWindowClick);
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseLeave(e: MouseEvent) {
|
||||
if (trigger.value && outside(e, trigger.value)) {
|
||||
visible.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
top: v-bind(top);
|
||||
left: v-bind(left);
|
||||
visibility: hidden;
|
||||
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -26,7 +26,6 @@
|
||||
height: 2em;
|
||||
-webkit-mask-size: 2em;
|
||||
mask-size: 2em;
|
||||
display: inline-block;
|
||||
background-color: var(--icon-bg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Download from "assets/svgs/download.svg";
|
||||
import RightHand from "assets/svgs/RH.png";
|
||||
import LeftFoot from "assets/svgs/LF.png";
|
||||
import RightFoot from "assets/svgs/RF.png";
|
||||
import Eraser from "assets/svgs/eraser-fill.svg";
|
||||
|
||||
export const IconUrlMap = {
|
||||
arrowClockwise: ArrowClockwise,
|
||||
@@ -18,6 +19,7 @@ export const IconUrlMap = {
|
||||
rh: RightHand,
|
||||
lf: LeftFoot,
|
||||
rf: RightFoot,
|
||||
eraser: Eraser,
|
||||
} as const;
|
||||
|
||||
export type IconName = keyof typeof IconUrlMap;
|
||||
|
||||
@@ -27,6 +27,20 @@ html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#dropdowns {
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#dropdowns * {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user