solid-pianoroll 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +457 -451
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +459 -453
- package/dist/esm/index.js.map +1 -1
- package/dist/source/PianoRoll.jsx +43 -16
- package/dist/source/PianoRollContext.jsx +3 -42
- package/dist/source/PianoRollGrid.jsx +22 -33
- package/dist/source/PianoRollKeys.jsx +10 -6
- package/dist/source/PianoRollNotes.jsx +91 -46
- package/dist/source/usePianoRoll.js +6 -3
- package/dist/source/viewport/HorizontalZoomControl.jsx +9 -0
- package/dist/source/viewport/ScrollContainer.jsx +87 -0
- package/dist/source/viewport/ScrollZoomViewPort.jsx +19 -0
- package/dist/source/viewport/VerticalZoomControl.jsx +12 -0
- package/dist/source/viewport/createViewPortDimension.js +39 -0
- package/dist/types/PianoRoll.d.ts +1 -0
- package/dist/types/PianoRollContext.d.ts +2 -22
- package/dist/types/PianoRollNotes.d.ts +1 -1
- package/dist/types/usePianoRoll.d.ts +1 -0
- package/dist/types/viewport/HorizontalZoomControl.d.ts +3 -0
- package/dist/types/viewport/ScrollContainer.d.ts +5 -0
- package/dist/types/viewport/ScrollZoomViewPort.d.ts +23 -0
- package/dist/types/viewport/VerticalZoomControl.d.ts +3 -0
- package/dist/types/viewport/createViewPortDimension.d.ts +31 -0
- package/package.json +7 -1
- package/dist/source/HorizontalZoomControl.jsx +0 -9
- package/dist/source/ScrollContainer.jsx +0 -138
- package/dist/source/VerticalZoomControl.jsx +0 -12
- package/dist/source/useViewPortScaler.js +0 -44
- package/dist/types/HorizontalZoomControl.d.ts +0 -2
- package/dist/types/ScrollContainer.d.ts +0 -5
- package/dist/types/VerticalZoomControl.d.ts +0 -2
- package/dist/types/useViewPortScaler.d.ts +0 -20
|
@@ -1,88 +1,133 @@
|
|
|
1
|
-
import { createMemo, Index, Show } from "solid-js";
|
|
1
|
+
import { createMemo, createSignal, Index, Show } from "solid-js";
|
|
2
2
|
import { usePianoRollContext } from "./PianoRollContext";
|
|
3
|
+
import { useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
4
|
+
import styles from "./PianoRollNotes.module.scss";
|
|
3
5
|
const PianoRollNotes = (props) => {
|
|
4
6
|
const context = usePianoRollContext();
|
|
7
|
+
const verticalViewPort = createMemo(() => useViewPortDimension("vertical"));
|
|
8
|
+
const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
|
|
5
9
|
const gridDivisionTicks = createMemo(() => (context.ppq * 4) / context.gridDivision);
|
|
6
10
|
const snapValueToGridIfEnabled = (value, altKey) => context.snapToGrid && !altKey
|
|
7
11
|
? Math.round(value / gridDivisionTicks()) * gridDivisionTicks()
|
|
8
12
|
: value;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const insertOrUpdateNote = (event) => {
|
|
14
|
+
const position = horizontalViewPort().calculatePosition(event.clientX);
|
|
15
|
+
const midi = 127 - Math.floor(verticalViewPort().calculatePosition(event.clientY));
|
|
16
|
+
const eventPositionTicks = Math.floor(position / gridDivisionTicks()) * gridDivisionTicks();
|
|
17
|
+
const velocity = 127;
|
|
18
|
+
const existingNote = newNote();
|
|
19
|
+
const ticks = existingNote?.ticks ?? eventPositionTicks;
|
|
20
|
+
const durationTicks = existingNote?.ticks
|
|
21
|
+
? eventPositionTicks - existingNote?.ticks
|
|
22
|
+
: gridDivisionTicks();
|
|
23
|
+
const note = { midi, ticks, durationTicks, velocity };
|
|
24
|
+
if (existingNote) {
|
|
25
|
+
context.onNoteChange?.(newNoteIndex(), note);
|
|
26
|
+
return newNoteIndex();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return context.onInsertNote?.(note) ?? -1;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const [isDragging, setIsDragging] = createSignal(false);
|
|
33
|
+
const [noteDragMode, setNoteDragMode] = createSignal();
|
|
34
|
+
const [newNoteIndex, setNewNoteIndex] = createSignal(-1);
|
|
35
|
+
const [isMouseDown, setIsMouseDown] = createSignal(false);
|
|
36
|
+
const newNote = createMemo(() => context.notes[newNoteIndex()]);
|
|
37
|
+
const getClasses = (noteDragMode) => {
|
|
38
|
+
return noteDragMode ? [styles.Note, styles[noteDragMode]] : [styles.Note];
|
|
39
|
+
};
|
|
40
|
+
return (<div class={styles.PianoRollNotes} ref={props.ref} onMouseDown={() => {
|
|
41
|
+
console.log("down");
|
|
42
|
+
setIsMouseDown(true);
|
|
43
|
+
}} onMouseMove={(event) => {
|
|
44
|
+
if (isDragging())
|
|
45
|
+
return;
|
|
46
|
+
if (!isMouseDown()) {
|
|
47
|
+
setNoteDragMode(undefined);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const index = insertOrUpdateNote(event);
|
|
51
|
+
console.log({ index });
|
|
52
|
+
setNewNoteIndex(index);
|
|
53
|
+
}} onMouseUp={(event) => {
|
|
54
|
+
setIsMouseDown(false);
|
|
55
|
+
if (!event.altKey)
|
|
56
|
+
return;
|
|
57
|
+
insertOrUpdateNote(event);
|
|
58
|
+
}} onDblClick={(event) => {
|
|
59
|
+
insertOrUpdateNote(event);
|
|
60
|
+
}} onClick={(event) => {
|
|
61
|
+
if (newNote()) {
|
|
62
|
+
setNewNoteIndex(-1);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!event.altKey)
|
|
66
|
+
return;
|
|
67
|
+
insertOrUpdateNote(event);
|
|
13
68
|
}}>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const horizontalVirtualDimensions = createMemo(() => context.verticalViewPort.getVirtualDimensions(127 - note().midi, 1));
|
|
22
|
-
const verticalVirtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(note().ticks, note().durationTicks));
|
|
23
|
-
return (<Show when={!!horizontalVirtualDimensions().size && !!verticalVirtualDimensions().size}>
|
|
24
|
-
<div class="PianoRoll-Note" onMouseMove={(event) => {
|
|
25
|
-
if (context.isDragging)
|
|
69
|
+
<Index each={context.notes}>
|
|
70
|
+
{(note, index) => {
|
|
71
|
+
const verticalVirtualDimensionss = createMemo(() => verticalViewPort().calculatePixelDimensions(127 - note().midi, 1));
|
|
72
|
+
const horizontalDimensions = createMemo(() => horizontalViewPort().calculatePixelDimensions(note().ticks, note().durationTicks));
|
|
73
|
+
return (<Show when={!!verticalVirtualDimensionss().size && !!horizontalDimensions().size}>
|
|
74
|
+
<div class={getClasses(noteDragMode()).join(" ")} onMouseMove={(event) => {
|
|
75
|
+
if (isDragging())
|
|
26
76
|
return;
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
77
|
+
event.stopPropagation();
|
|
78
|
+
const relativeX = horizontalViewPort().calculatePixelValue(horizontalViewPort().calculatePosition(event.clientX));
|
|
79
|
+
const noteStartX = horizontalViewPort().calculatePixelValue(note().ticks);
|
|
80
|
+
const noteEndX = horizontalViewPort().calculatePixelValue(note().ticks + note().durationTicks);
|
|
81
|
+
setNoteDragMode(relativeX - noteStartX < 3
|
|
31
82
|
? "trimStart"
|
|
32
83
|
: noteEndX - relativeX < 3
|
|
33
84
|
? "trimEnd"
|
|
34
85
|
: "move");
|
|
35
|
-
}} onDblClick={() => {
|
|
86
|
+
}} onDblClick={(event) => {
|
|
87
|
+
event.stopPropagation();
|
|
36
88
|
context.onRemoveNote?.(index);
|
|
37
89
|
}} onMouseDown={(event) => {
|
|
38
|
-
|
|
39
|
-
|
|
90
|
+
event.stopPropagation();
|
|
91
|
+
setIsDragging(true);
|
|
92
|
+
const initialPosition = horizontalViewPort().calculatePosition(event.clientX);
|
|
40
93
|
const diffPosition = initialPosition - note().ticks;
|
|
41
94
|
const handleMouseMove = (mouseMoveEvent) => {
|
|
42
|
-
const ticks = snapValueToGridIfEnabled(Math.max(
|
|
95
|
+
const ticks = snapValueToGridIfEnabled(Math.max(horizontalViewPort().calculatePosition(mouseMoveEvent.clientX) -
|
|
43
96
|
diffPosition, 0), mouseMoveEvent.altKey);
|
|
44
97
|
context.onNoteChange?.(index, {
|
|
45
98
|
...note(),
|
|
46
|
-
...(
|
|
47
|
-
midi: Math.round(127 -
|
|
99
|
+
...(noteDragMode() === "move" && {
|
|
100
|
+
midi: Math.round(127 - verticalViewPort().calculatePosition(mouseMoveEvent.clientY)),
|
|
48
101
|
}),
|
|
49
|
-
...((
|
|
50
|
-
context.noteDragMode === "trimStart") && {
|
|
102
|
+
...((noteDragMode() === "move" || noteDragMode() === "trimStart") && {
|
|
51
103
|
ticks,
|
|
52
104
|
}),
|
|
53
|
-
...(
|
|
105
|
+
...(noteDragMode() === "trimStart" && {
|
|
54
106
|
durationTicks: note().durationTicks + note().ticks - ticks,
|
|
55
107
|
}),
|
|
56
|
-
...(
|
|
57
|
-
durationTicks: snapValueToGridIfEnabled(
|
|
108
|
+
...(noteDragMode() === "trimEnd" && {
|
|
109
|
+
durationTicks: snapValueToGridIfEnabled(horizontalViewPort().calculatePosition(mouseMoveEvent.clientX) -
|
|
58
110
|
note().ticks, mouseMoveEvent.altKey),
|
|
59
111
|
}),
|
|
60
112
|
});
|
|
61
113
|
};
|
|
62
114
|
const handleMouseUp = () => {
|
|
63
|
-
|
|
115
|
+
setIsDragging(false);
|
|
64
116
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
65
117
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
66
118
|
};
|
|
67
119
|
window.addEventListener("mousemove", handleMouseMove);
|
|
68
120
|
window.addEventListener("mouseup", handleMouseUp);
|
|
69
121
|
}} style={{
|
|
70
|
-
"z-index": 2,
|
|
71
|
-
position: "absolute",
|
|
72
|
-
"box-sizing": "border-box",
|
|
73
|
-
top: `${horizontalVirtualDimensions().offset}px`,
|
|
74
|
-
height: `${horizontalVirtualDimensions().size}px`,
|
|
75
|
-
left: `${verticalVirtualDimensions().offset}px`,
|
|
76
|
-
width: `${verticalVirtualDimensions().size}px`,
|
|
77
|
-
"border-width": "0.5px",
|
|
78
|
-
"border-style": "solid",
|
|
79
122
|
"background-color": `rgba(255,0,0, ${(128 + note().velocity) / 256})`,
|
|
80
|
-
|
|
123
|
+
top: `${verticalVirtualDimensionss().offset}px`,
|
|
124
|
+
height: `${verticalVirtualDimensionss().size}px`,
|
|
125
|
+
left: `${horizontalDimensions().offset}px`,
|
|
126
|
+
width: `${horizontalDimensions().size}px`,
|
|
81
127
|
}}></div>
|
|
82
|
-
|
|
128
|
+
</Show>);
|
|
83
129
|
}}
|
|
84
|
-
|
|
85
|
-
</div>
|
|
130
|
+
</Index>
|
|
86
131
|
</div>);
|
|
87
132
|
};
|
|
88
133
|
export default PianoRollNotes;
|
|
@@ -8,7 +8,7 @@ const usePianoRoll = () => {
|
|
|
8
8
|
const [gridDivision, onGridDivisionChange] = createSignal(16);
|
|
9
9
|
const [snapToGrid, onSnapToGridChange] = createSignal(true);
|
|
10
10
|
const [notes, onNotesChange] = createSignal([]);
|
|
11
|
-
const [duration,
|
|
11
|
+
const [duration, onDurationChange] = createSignal(0);
|
|
12
12
|
const onNoteChange = (index, note) => {
|
|
13
13
|
onNotesChange([...notes().slice(0, index), note, ...notes().splice(index + 1)]);
|
|
14
14
|
};
|
|
@@ -21,8 +21,10 @@ const usePianoRoll = () => {
|
|
|
21
21
|
onNotesChange([...notes().slice(0, index), ...notes().splice(index + 1)]);
|
|
22
22
|
};
|
|
23
23
|
createEffect(() => {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const minDuration = Math.max((notes()[notes().length - 1]?.ticks ?? 0) + (notes()[notes().length - 1]?.durationTicks ?? 0), ppq() * 4 * 4);
|
|
25
|
+
if (duration() < minDuration) {
|
|
26
|
+
onDurationChange(minDuration);
|
|
27
|
+
}
|
|
26
28
|
});
|
|
27
29
|
return {
|
|
28
30
|
ppq,
|
|
@@ -45,6 +47,7 @@ const usePianoRoll = () => {
|
|
|
45
47
|
onInsertNote,
|
|
46
48
|
onRemoveNote,
|
|
47
49
|
duration,
|
|
50
|
+
onDurationChange,
|
|
48
51
|
};
|
|
49
52
|
};
|
|
50
53
|
export default usePianoRoll;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { usePianoRollContext } from "../PianoRollContext";
|
|
2
|
+
const HorizontalZoomControl = (props) => {
|
|
3
|
+
const context = usePianoRollContext();
|
|
4
|
+
return (<input max="500" min="1" step={0.01} {...props} value={context.zoom} onInput={(event) => context.onZoomChange?.(event.currentTarget.valueAsNumber)} type="range" style={{
|
|
5
|
+
"margin-left": "50px",
|
|
6
|
+
"margin-right": "16px",
|
|
7
|
+
}}/>);
|
|
8
|
+
};
|
|
9
|
+
export default HorizontalZoomControl;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createEffect, createMemo } from "solid-js";
|
|
2
|
+
import { useViewPortDimension } from "./ScrollZoomViewPort";
|
|
3
|
+
const ScrollContainer = (props) => {
|
|
4
|
+
let scrollContentRef;
|
|
5
|
+
const verticalViewPort = createMemo(() => useViewPortDimension("vertical"));
|
|
6
|
+
const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
|
|
7
|
+
const handleScroll = (event) => {
|
|
8
|
+
event.preventDefault();
|
|
9
|
+
if (didUpdateScroll) {
|
|
10
|
+
didUpdateScroll = false;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
|
|
14
|
+
const maxPosition = horizontalViewPort().calculateMaxPosition();
|
|
15
|
+
const height = verticalViewPort().pixelSize;
|
|
16
|
+
const width = horizontalViewPort().pixelSize;
|
|
17
|
+
const { scrollTop, scrollLeft, scrollWidth, scrollHeight } = event.currentTarget;
|
|
18
|
+
const scrollTopAmount = scrollTop / (scrollHeight - height);
|
|
19
|
+
const scrollLeftAmount = scrollLeft / (scrollWidth - width);
|
|
20
|
+
verticalViewPort().onPositionChange?.(maxVerticalPosition * scrollTopAmount);
|
|
21
|
+
horizontalViewPort().onPositionChange?.(maxPosition * scrollLeftAmount);
|
|
22
|
+
};
|
|
23
|
+
const handleWheel = (event) => {
|
|
24
|
+
if (event.altKey) {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
|
|
27
|
+
horizontalViewPort()?.onZoomChange?.(horizontalViewPort().zoom * (1 + event.deltaX / horizontalViewPort().pixelSize));
|
|
28
|
+
const maxPosition = horizontalViewPort().calculateMaxPosition();
|
|
29
|
+
horizontalViewPort()?.onPositionChange?.(Math.min(maxPosition, horizontalViewPort()?.position));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
verticalViewPort()?.onZoomChange?.(verticalViewPort().zoom * (1 + event.deltaY / verticalViewPort().pixelSize));
|
|
33
|
+
const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
|
|
34
|
+
verticalViewPort()?.onPositionChange?.(Math.min(maxVerticalPosition, verticalViewPort()?.position));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
let didUpdateScroll = false;
|
|
39
|
+
createEffect(() => {
|
|
40
|
+
const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
|
|
41
|
+
const maxPosition = horizontalViewPort().calculateMaxPosition();
|
|
42
|
+
const scrollTopAmount = maxVerticalPosition > 0 ? verticalViewPort().position / maxVerticalPosition : 0;
|
|
43
|
+
const scrollLeftAmount = maxPosition > 0 ? horizontalViewPort().position / maxPosition : 0;
|
|
44
|
+
const height = verticalViewPort().pixelSize;
|
|
45
|
+
const width = horizontalViewPort().pixelSize;
|
|
46
|
+
if (!scrollContentRef?.parentElement)
|
|
47
|
+
return;
|
|
48
|
+
const scrollDivHeight = verticalViewPort().zoom * verticalViewPort().pixelSize;
|
|
49
|
+
const scrollTop = scrollTopAmount * (scrollDivHeight - height);
|
|
50
|
+
const scrollDivWidth = horizontalViewPort().zoom * horizontalViewPort().pixelSize;
|
|
51
|
+
const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
|
|
52
|
+
didUpdateScroll = true;
|
|
53
|
+
scrollContentRef.style.height = `${scrollDivHeight}px`;
|
|
54
|
+
scrollContentRef.style.width = `${scrollDivWidth}px`;
|
|
55
|
+
scrollContentRef.parentElement.scrollTo({
|
|
56
|
+
left: scrollLeft,
|
|
57
|
+
top: scrollTop,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
return (<div ref={props.ref} class="PianoRoll-Scroller" style={{
|
|
61
|
+
height: "100%",
|
|
62
|
+
width: "100%",
|
|
63
|
+
"z-index": 4,
|
|
64
|
+
overflow: "scroll",
|
|
65
|
+
"pointer-events": "auto",
|
|
66
|
+
}} onScroll={handleScroll} onWheel={handleWheel}>
|
|
67
|
+
<div ref={scrollContentRef}>
|
|
68
|
+
<div style={{
|
|
69
|
+
top: 0,
|
|
70
|
+
left: 0,
|
|
71
|
+
height: `${verticalViewPort().pixelSize}px`,
|
|
72
|
+
width: `${horizontalViewPort().pixelSize}px`,
|
|
73
|
+
position: "sticky",
|
|
74
|
+
display: "flex",
|
|
75
|
+
}}>
|
|
76
|
+
<div style={{
|
|
77
|
+
position: "relative",
|
|
78
|
+
height: `${verticalViewPort().pixelSize}px`,
|
|
79
|
+
width: `${horizontalViewPort().pixelSize}px`,
|
|
80
|
+
}}>
|
|
81
|
+
{props.children}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>);
|
|
86
|
+
};
|
|
87
|
+
export default ScrollContainer;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, useContext } from "solid-js";
|
|
2
|
+
import createViewPortDimension from "./createViewPortDimension";
|
|
3
|
+
export const ScrollZoomViewPort = (props) => {
|
|
4
|
+
const viewPorts = Object.entries(props.dimensions).map(([name, viewPortProps]) => createViewPortDimension(() => ({ name: name, ...viewPortProps() })));
|
|
5
|
+
const parentViewPorts = useContext(ScrollZoomViewPortContext);
|
|
6
|
+
return (<ScrollZoomViewPortContext.Provider value={[...parentViewPorts, ...viewPorts]}>
|
|
7
|
+
{props.children}
|
|
8
|
+
</ScrollZoomViewPortContext.Provider>);
|
|
9
|
+
};
|
|
10
|
+
const ScrollZoomViewPortContext = createContext([]);
|
|
11
|
+
export const useViewPortDimension = (name) => {
|
|
12
|
+
const viewPorts = useContext(ScrollZoomViewPortContext);
|
|
13
|
+
const viewPort = name
|
|
14
|
+
? viewPorts.find((viewPort) => viewPort.name === name)
|
|
15
|
+
: viewPorts[viewPorts.length - 1];
|
|
16
|
+
if (!viewPort)
|
|
17
|
+
throw new Error(`ViewPort dimension "${name}" not found.`);
|
|
18
|
+
return viewPort;
|
|
19
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { usePianoRollContext } from "../PianoRollContext";
|
|
2
|
+
const VerticalZoomControl = (props) => {
|
|
3
|
+
const context = usePianoRollContext();
|
|
4
|
+
return (<input min="1" max="11" step={0.01} {...props} value={context.verticalZoom} onInput={(event) => context.onVerticalZoomChange?.(event.currentTarget.valueAsNumber)} type="range" {...{ orient: "vertical" }} style={{
|
|
5
|
+
width: "16px",
|
|
6
|
+
...{
|
|
7
|
+
"writing-mode": "bt-lr" /* IE */,
|
|
8
|
+
"-webkit-appearance": "slider-vertical" /* WebKit */,
|
|
9
|
+
},
|
|
10
|
+
}}/>);
|
|
11
|
+
};
|
|
12
|
+
export default VerticalZoomControl;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createEffect } from "solid-js";
|
|
2
|
+
import { createStore } from "solid-js/store";
|
|
3
|
+
export default function createViewPortDimension(getState) {
|
|
4
|
+
const getStateWithFunctions = () => ({
|
|
5
|
+
...getState(),
|
|
6
|
+
calculatePixelValue,
|
|
7
|
+
calculatePosition,
|
|
8
|
+
calculatePixelDimensions,
|
|
9
|
+
calculateVisibleRange,
|
|
10
|
+
calculateMaxPosition,
|
|
11
|
+
});
|
|
12
|
+
const [state, setState] = createStore(getStateWithFunctions());
|
|
13
|
+
createEffect(() => setState(getStateWithFunctions()));
|
|
14
|
+
function calculatePixelValue(position = state.position) {
|
|
15
|
+
const virtualSize = state.pixelSize * state.zoom;
|
|
16
|
+
return (position / state.range) * virtualSize;
|
|
17
|
+
}
|
|
18
|
+
function calculatePixelOffset(position) {
|
|
19
|
+
return calculatePixelValue(position) - calculatePixelValue(state.position);
|
|
20
|
+
}
|
|
21
|
+
function calculatePosition(offset) {
|
|
22
|
+
const percentX = (offset - state.pixelOffset) / state.pixelSize;
|
|
23
|
+
const position = state.position + percentX * calculateVisibleRange();
|
|
24
|
+
return position;
|
|
25
|
+
}
|
|
26
|
+
function calculateVisibleRange() {
|
|
27
|
+
return state.range / state.zoom;
|
|
28
|
+
}
|
|
29
|
+
function calculateMaxPosition() {
|
|
30
|
+
return state.range - state.range / state.zoom;
|
|
31
|
+
}
|
|
32
|
+
function calculatePixelDimensions(position, length) {
|
|
33
|
+
const offset = calculatePixelOffset(position);
|
|
34
|
+
const size = calculatePixelValue(length);
|
|
35
|
+
return { offset, size };
|
|
36
|
+
}
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
export const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
import { JSX } from "solid-js";
|
|
2
1
|
import { PianoRollProps } from "./PianoRoll";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type NoteDragMode = "trimStart" | "move" | "trimEnd" | undefined;
|
|
6
|
-
type PianoRollContext = {
|
|
7
|
-
horizontalViewPort: ViewPortScaler;
|
|
8
|
-
verticalViewPort: ViewPortScaler;
|
|
9
|
-
clientRect: ClientRect;
|
|
10
|
-
notesContainer?: HTMLDivElement;
|
|
11
|
-
isDragging: boolean;
|
|
12
|
-
onIsDraggingChange: (isDragging: boolean) => void;
|
|
13
|
-
noteDragMode: NoteDragMode;
|
|
14
|
-
onNoteDragModeChange: (isDragging: NoteDragMode) => void;
|
|
15
|
-
} & PianoRollProps;
|
|
16
|
-
declare const PianoRollContext: import("solid-js").Context<PianoRollContext>;
|
|
17
|
-
export declare const PianoRollContextProvider: (props: {
|
|
18
|
-
scrollContainer?: HTMLDivElement;
|
|
19
|
-
notesContainer?: HTMLDivElement;
|
|
20
|
-
children: JSX.Element;
|
|
21
|
-
} & PianoRollProps) => JSX.Element;
|
|
22
|
-
export declare const usePianoRollContext: () => PianoRollContext;
|
|
23
|
-
export {};
|
|
2
|
+
export declare const PianoRollContextProvider: import("solid-js/types/reactive/signal").ContextProviderComponent<PianoRollProps>;
|
|
3
|
+
export declare const usePianoRollContext: () => PianoRollProps;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ParentProps } from "solid-js";
|
|
2
|
+
import { ViewPortDimensionName, ViewPortDimensionState } from "./createViewPortDimension";
|
|
3
|
+
export declare const ScrollZoomViewPort: (props: ParentProps<{
|
|
4
|
+
dimensions: Record<ViewPortDimensionName, () => Omit<ViewPortDimensionState, "name">>;
|
|
5
|
+
}>) => import("solid-js").JSX.Element;
|
|
6
|
+
export declare const useViewPortDimension: (name: ViewPortDimensionName) => {
|
|
7
|
+
calculatePixelValue: (position?: number) => number;
|
|
8
|
+
calculatePosition: (offset: number) => number;
|
|
9
|
+
calculatePixelDimensions: (position: number, length: number) => {
|
|
10
|
+
offset: number;
|
|
11
|
+
size: number;
|
|
12
|
+
};
|
|
13
|
+
calculateVisibleRange: () => number;
|
|
14
|
+
calculateMaxPosition: () => number;
|
|
15
|
+
position: number;
|
|
16
|
+
range: number;
|
|
17
|
+
pixelOffset: number;
|
|
18
|
+
pixelSize: number;
|
|
19
|
+
name: ViewPortDimensionName;
|
|
20
|
+
onZoomChange?: (zoom: number) => void;
|
|
21
|
+
onPositionChange?: (zoom: number) => void;
|
|
22
|
+
zoom: number;
|
|
23
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ViewPortDimension = ReturnType<typeof createViewPortDimension>;
|
|
2
|
+
export type ViewPortDimensionName = "horizontal" | "vertical";
|
|
3
|
+
export type ViewPortDimensionState = {
|
|
4
|
+
position: number;
|
|
5
|
+
range: number;
|
|
6
|
+
pixelOffset: number;
|
|
7
|
+
pixelSize: number;
|
|
8
|
+
name: ViewPortDimensionName;
|
|
9
|
+
onZoomChange?: (zoom: number) => void;
|
|
10
|
+
onPositionChange?: (zoom: number) => void;
|
|
11
|
+
zoom: number;
|
|
12
|
+
};
|
|
13
|
+
export default function createViewPortDimension(getState: () => ViewPortDimensionState): {
|
|
14
|
+
calculatePixelValue: (position?: number) => number;
|
|
15
|
+
calculatePosition: (offset: number) => number;
|
|
16
|
+
calculatePixelDimensions: (position: number, length: number) => {
|
|
17
|
+
offset: number;
|
|
18
|
+
size: number;
|
|
19
|
+
};
|
|
20
|
+
calculateVisibleRange: () => number;
|
|
21
|
+
calculateMaxPosition: () => number;
|
|
22
|
+
position: number;
|
|
23
|
+
range: number;
|
|
24
|
+
pixelOffset: number;
|
|
25
|
+
pixelSize: number;
|
|
26
|
+
name: ViewPortDimensionName;
|
|
27
|
+
onZoomChange?: (zoom: number) => void;
|
|
28
|
+
onPositionChange?: (zoom: number) => void;
|
|
29
|
+
zoom: number;
|
|
30
|
+
};
|
|
31
|
+
export declare const clamp: (value: number, min: number, max: number) => number;
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.
|
|
2
|
+
"version": "0.0.10",
|
|
3
3
|
"name": "solid-pianoroll",
|
|
4
4
|
"description": "Pianoroll UI Control for Solid JS apps",
|
|
5
5
|
"license": "MIT",
|
|
@@ -50,6 +50,12 @@
|
|
|
50
50
|
"solid-js": ">=1.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
+
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
|
54
|
+
"@typescript-eslint/parser": "^5.47.1",
|
|
55
|
+
"eslint": "^8.31.0",
|
|
56
|
+
"eslint-config-prettier": "^8.5.0",
|
|
57
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
58
|
+
"eslint-plugin-unused-imports": "^2.0.0",
|
|
53
59
|
"postcss": "^8.4.20",
|
|
54
60
|
"prettier": "2.8.1",
|
|
55
61
|
"rollup": "^3.7.2",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { usePianoRollContext } from "./PianoRollContext";
|
|
2
|
-
const HorizontalZoomControl = () => {
|
|
3
|
-
const context = usePianoRollContext();
|
|
4
|
-
return (<input value={context.zoom} onInput={(event) => context.onZoomChange?.(event.currentTarget.valueAsNumber)} type="range" max="500" min="1" step={0.01} style={{
|
|
5
|
-
"margin-left": "50px",
|
|
6
|
-
"margin-right": "16px",
|
|
7
|
-
}}/>);
|
|
8
|
-
};
|
|
9
|
-
export default HorizontalZoomControl;
|