aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/selection.tsx6
-rw-r--r--pages/editor.tsx66
-rw-r--r--styles/selection.css8
-rw-r--r--timeline.ts5
4 files changed, 50 insertions, 35 deletions
diff --git a/components/selection.tsx b/components/selection.tsx
index 4be9767..618d854 100644
--- a/components/selection.tsx
+++ b/components/selection.tsx
@@ -90,14 +90,16 @@ export default function Selection(props: {
right?: slideTypes;
className?: string;
widthOffset?: number;
+ visibility?: number;
}) {
- var small = (props.width + props.widthOffset) < 24 || props.height < 24 || !props.left || !props.right;
+ var small = !props.left || !props.right;
return <div
- className={'selection ' + props.className}
+ className={'selection ' + (props.className || '')}
style={{
width: `calc(var(--zoom) * ${props.frameWidth} * 1px + 12px + ${props.widthOffset} * 1px)`,
height: props.height,
'--corner-size': small ? '6px' : '12px',
+ '--visibility': props.visibility,
} as CSSProperties}
>
<div className='background fill left posabs dispinbl l0' />
diff --git a/pages/editor.tsx b/pages/editor.tsx
index 872e9f7..635934e 100644
--- a/pages/editor.tsx
+++ b/pages/editor.tsx
@@ -195,6 +195,7 @@ interface selectionPos {
frameWidth: number;
startOffset: number;
widthOffset: number;
+ visibility: number;
}
var selectionPos: SpringValues<selectionPos>,
selectionPosAPI: SpringRef<selectionPos>;
@@ -211,6 +212,7 @@ function select(slides: anySlide[]) {
},
});
setSetting('default');
+ selectionPosAPI({ visibility: 0 });
} else {
var left = slides[0];
var right = slides[slides.length - 1];
@@ -237,6 +239,7 @@ function select(slides: anySlide[]) {
},
});
setSetting('slide');
+ selectionPosAPI({ visibility: 1 });
}
}
@@ -324,6 +327,9 @@ function TimelineKeyframe(props: {
modifySlide({ frame: frame + endOffset });
modifySlide({ beginFrame: frame + startOffset });
}
+ var end = props.slide;
+ var begin = new loopBeginSlide(end as loopSlide);
+ select([begin, end]);
} else {
if (intentional) {
api.start({ frame });
@@ -339,22 +345,24 @@ function TimelineKeyframe(props: {
if (props.slide.type == 'loop') {
// loop start
- useDrag(({ xy: [x, _y] }) => {
- var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom.value)) - 1);
-
- api.start({ begin: frame });
-
- modifySlide({ beginFrame: frame });
- }, { domTarget: loopStartRef, eventOptions: { passive: false } });
+ useDrag(({ xy: [x, _y], intentional }) => {
+ if (intentional) {
+ var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom.value)) - 1);
+ api.start({ begin: frame });
+ modifySlide({ beginFrame: frame });
+ }
+ select([new loopBeginSlide(props.slide as loopSlide)]);
+ }, { domTarget: loopStartRef, eventOptions: { passive: false }, threshold: 10, triggerAllEvents: true });
// loop end
- useDrag(({ xy: [x, _y] }) => {
- var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom.value)) - 1);
-
- api.start({ frame });
-
- modifySlide({ frame });
- }, { domTarget: loopEndRef, eventOptions: { passive: false } });
+ useDrag(({ xy: [x, _y], intentional }) => {
+ if (intentional) {
+ var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom.value)) - 1);
+ api.start({ frame });
+ modifySlide({ frame });
+ }
+ select([props.slide]);
+ }, { domTarget: loopEndRef, eventOptions: { passive: false }, threshold: 10, triggerAllEvents: true });
}
var mouseUpListener = useRef(null);
@@ -399,9 +407,6 @@ function TimelineLabels() {
}
function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
- var workingTimeline = useHookstate(global).timeline.workingTimeline;
- var tool = useHookstate(global).timeline.tool;
-
var selection = useHookstate(global).selection;
[selectionPos, selectionPosAPI] = useSpring(() => ({
@@ -414,6 +419,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
frameWidth: 0,
startOffset: 0,
widthOffset: 0,
+ visibility: 0,
config: { mass: 0.5, tension: 500, friction: 20 },
}));
@@ -428,7 +434,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
switch (slide.value.type as slideTypes | 'loopBegin') {
case 'loopBegin': {
if (!api) break;
- var loop = workingTimeline.value.find(s => s.id == slide.value.id) as loopSlide;
+ var loop = global.timeline.workingTimeline.value.find(s => s.id == slide.value.id) as loopSlide;
var begin = loop.beginFrame + frameOffset;
api.start({ begin });
@@ -446,7 +452,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
api.start({ frame });
if (last) {
- workingTimeline.find(s => s.value.id == slide.value.id).frame.set(frame);
+ global.timeline.workingTimeline.find(s => s.value.id == slide.value.id).frame.set(frame);
global.update.refreshLiveTimeline.value();
}
}
@@ -458,11 +464,14 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
}, { domTarget: selectionRef, eventOptions: { passive: false } });
useDrag(({ xy: [x, y], initial: [bx, by], first, last, movement: [ox, oy] }) => {
- if (tool.value != 'cursor') return;
+ if (global.timeline.tool.value != 'cursor') return;
var minDistance = 5; // minimal drag distance in pixels to register selection
var distanceTraveled = Math.sqrt(ox ** 2 + oy ** 2);
- if (global.selection.hidden.value && distanceTraveled > minDistance) global.selection.hidden.set(false);
+ if (global.selection.hidden.value && distanceTraveled > minDistance) {
+ global.selection.hidden.set(false);
+ selectionPosAPI.start({ visibility: 1 });
+ }
if (global.selection.type.left.value) global.selection.type.left.set(null);
if (global.selection.type.right.value) global.selection.type.right.set(null);
if (global.selection.placed.value) global.selection.placed.set(false);
@@ -492,7 +501,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
var frameWidth = Math.abs(sx) / zoom;
var startingFrame = x1 / zoom;
- selectionPosAPI[first && global.selection.hidden ? 'set' : 'start']({
+ selectionPosAPI[first && global.selection.hidden.value ? 'set' : 'start']({
x1,
y1,
x2,
@@ -506,15 +515,14 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
if (distanceTraveled <= minDistance) {
setSetting('default');
global.selection.hidden.set(true);
+ selectionPosAPI.start({ visibility: 0 });
} else {
var endingFrame = startingFrame + frameWidth;
var expandedTimeline = new Array(...project.timeline.slides.value);
for (let i = 0; i < expandedTimeline.length; i++) {
var slide = expandedTimeline[i];
if (slide.type != 'loop') continue;
- var beginFrame = (slide as loopSlide).beginFrame;
- expandedTimeline.splice(i, 0, new loopBeginSlide(beginFrame));
- expandedTimeline[i].id = expandedTimeline[i + 1].id;
+ expandedTimeline.splice(i, 0, new loopBeginSlide(slide as loopSlide));
i++;
}
@@ -530,10 +538,10 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
useMousetrap(['del', 'backspace'], () => {
if (!global.selection.placed) return;
- var selection = global.selection.slides.attach(Downgraded).value
+ var sel = global.selection.slides.attach(Downgraded).value
.map(s => ({ id: s.id.toString(), type: s.type.toString() }))
.filter(s => slideTypes.includes(s.type));
- selection.forEach(slide => global.timeline.workingTimeline.find(s => s.value?.id == slide.id).set(none));
+ sel.forEach(slide => global.timeline.workingTimeline.find(s => s.value?.id == slide.id).set(none));
global.update.refreshLiveTimeline.value();
setSetting('default');
@@ -542,6 +550,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
hidden: true,
slides: [],
});
+ selectionPosAPI.start({ visibility: 0 });
});
function CustomSelection(props: {
@@ -551,6 +560,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
y2: number;
widthOffset: number;
frameWidth: number;
+ visibility: number;
className: string;
}) {
return <Selection
@@ -561,6 +571,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
left={selection.type.left.get()}
right={selection.type.right.get()}
widthOffset={props.widthOffset}
+ visibility={props.visibility}
/>;
}
var AnimatedSelection = animated(props => <CustomSelection {...props} />);
@@ -584,6 +595,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
y2={selectionPos.y2}
widthOffset={selectionPos.widthOffset}
frameWidth={selectionPos.frameWidth}
+ visibility={selectionPos.visibility}
className={'' + (selection.active.get() ? 'active ' : '') + (selection.hidden.get() ? 'hidden ' : '')}
/>
</animated.div>;
diff --git a/styles/selection.css b/styles/selection.css
index 6e16cee..a139367 100644
--- a/styles/selection.css
+++ b/styles/selection.css
@@ -2,8 +2,8 @@
--selection-color: var(--gruble);
--corner-size: 12px;
filter: drop-shadow(0px 0px 16px var(--selection-color));
- transition-duration: 100ms;
- transition-property: transform, opacity;
+ transform: scale(calc(0.5 * var(--visibility) + 0.5));
+ opacity: var(--visibility);
}
.selection .bar.top,
@@ -57,7 +57,3 @@
opacity: .15;
}
-.selection.hidden {
- transform: scale(0.5);
- opacity: 0;
-}
diff --git a/timeline.ts b/timeline.ts
index 7f673c5..f5f8066 100644
--- a/timeline.ts
+++ b/timeline.ts
@@ -32,6 +32,11 @@ export class loopSlide extends slide {
export class loopBeginSlide extends slide {
type = 'loopBegin' as slideTypes;
+
+ constructor(public parent: loopSlide) {
+ super(parent.beginFrame);
+ this.id = parent.id;
+ }
}
export var toolToSlide = {