diff options
author | Sophie Zhang <sophie_zhang@brown.edu> | 2024-03-07 01:32:34 -0500 |
---|---|---|
committer | Sophie Zhang <sophie_zhang@brown.edu> | 2024-03-07 01:32:34 -0500 |
commit | 216cdb95bb106893f3e1937ba86546f6455ee688 (patch) | |
tree | 05328514fdeb35354fe4295e3d29aba3d114a349 /src | |
parent | 3bcbf32d54af121a84e86c6bd1c99575bfc99859 (diff) |
feat: added preliminary ease function editor
Diffstat (limited to 'src')
-rw-r--r-- | src/Utils.ts | 1 | ||||
-rw-r--r-- | src/client/views/nodes/trails/CubicBezierEditor.tsx | 134 | ||||
-rw-r--r-- | src/client/views/nodes/trails/PresBox.scss | 14 | ||||
-rw-r--r-- | src/client/views/nodes/trails/PresBox.tsx | 47 |
4 files changed, 182 insertions, 14 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index e8bd35ac4..5f60e5f2a 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -644,6 +644,7 @@ const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number }; export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) { + console.log('smooth scroll'); stopper?.(); const elements = element instanceof HTMLElement ? [element] : element; const starts = elements.map(element => element.scrollTop); diff --git a/src/client/views/nodes/trails/CubicBezierEditor.tsx b/src/client/views/nodes/trails/CubicBezierEditor.tsx new file mode 100644 index 000000000..0744709aa --- /dev/null +++ b/src/client/views/nodes/trails/CubicBezierEditor.tsx @@ -0,0 +1,134 @@ +import React, { useEffect, useState } from 'react'; + +type Props = { + setFunc: (newPoints: { p1: number[]; p2: number[] }) => void; +}; + +const ANIMATION_DURATION = 750; +// const ANIMATION_TIMING_FUNC = "cubic-bezier(.42,.97,.52,1.49)"; +const ANIMATION_TIMING_FUNC = 'cubic-bezier(0.3, .2, .2, 1.4)'; +const CONTAINER_WIDTH = 200; +const EDITOR_WIDTH = 100; +const OFFSET = (CONTAINER_WIDTH - EDITOR_WIDTH) / 2; + +const CubicBezierEditor = ({ setFunc }: Props) => { + const [animating, setAnimating] = useState(false); + const [cPoints, setCPoints] = useState({ p1: [0.67, 0.2], p2: [0.37, 0.88] }); + const [c1Down, setC1Down] = useState(false); + const [c2Down, setC2Down] = useState(false); + + const roundToHundredth = (num: number) => { + return Math.round(num * 100) / 100; + }; + + useEffect(() => { + if (animating) { + setTimeout(() => { + setAnimating(false); + }, ANIMATION_DURATION * 2); + } + }, [animating]); + + useEffect(() => { + if (!c1Down) return; + window.addEventListener('pointerup', () => { + setC1Down(false); + }); + const handlePointerMove = (e: PointerEvent) => { + const newX = cPoints.p1[0] + e.movementX / EDITOR_WIDTH; + if (newX < 0 || newX > 1) { + return; + } + + setCPoints(prev => ({ + ...prev, + p1: [roundToHundredth(prev.p1[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(prev.p1[1] - e.movementY / EDITOR_WIDTH)], + })); + setFunc({ + ...cPoints, + p1: [roundToHundredth(cPoints.p1[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(cPoints.p1[1] - e.movementY / EDITOR_WIDTH)], + }); + }; + + window.addEventListener('pointermove', handlePointerMove); + + return () => window.removeEventListener('pointermove', handlePointerMove); + }, [c1Down, cPoints]); + + useEffect(() => { + if (!c2Down) return; + window.addEventListener('pointerup', () => { + setC2Down(false); + }); + const handlePointerMove = (e: PointerEvent) => { + const newX = cPoints.p2[0] + e.movementX / EDITOR_WIDTH; + if (newX < 0 || newX > 1) { + return; + } + + setCPoints(prev => ({ + ...prev, + p2: [roundToHundredth(prev.p2[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(prev.p2[1] - e.movementY / EDITOR_WIDTH)], + })); + setFunc({ + ...cPoints, + p1: [roundToHundredth(cPoints.p2[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(cPoints.p2[1] - e.movementY / EDITOR_WIDTH)], + }); + }; + + window.addEventListener('pointermove', handlePointerMove); + + return () => window.removeEventListener('pointermove', handlePointerMove); + }, [c2Down, cPoints]); + + return ( + <div> + <svg className="presBox-bezier-editor" width={`${CONTAINER_WIDTH}`} height={`${CONTAINER_WIDTH}`} xmlns="http://www.w3.org/2000/svg"> + {/* Outlines */} + <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${EDITOR_WIDTH + OFFSET}`} y2={`${0 + OFFSET}`} stroke="#c1c1c1" strokeWidth="1" /> + {/* Box Outline */} + <rect x={`${0 + OFFSET}`} y={`${0 + OFFSET}`} width={EDITOR_WIDTH} height={EDITOR_WIDTH} stroke="#c5c5c5" fill="transparent" strokeWidth="1" /> + {/* Editor */} + <path + d={`M ${0 + OFFSET} ${EDITOR_WIDTH + OFFSET} C ${cPoints.p1[0] * EDITOR_WIDTH + OFFSET} ${EDITOR_WIDTH - cPoints.p1[1] * EDITOR_WIDTH + OFFSET}, ${ + cPoints.p2[0] * EDITOR_WIDTH + OFFSET + } ${EDITOR_WIDTH - cPoints.p2[1] * EDITOR_WIDTH + OFFSET}, ${EDITOR_WIDTH + OFFSET} ${0 + OFFSET}`} + stroke="#ffffff" + fill="transparent" + /> + {/* Bottom left */} + <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${cPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - cPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" /> + <circle + cx={`${cPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} + cy={`${EDITOR_WIDTH - cPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} + r="5" + fill={`${c1Down ? '#3fa9ff' : '#ffffff'}`} + onPointerDown={e => { + e.stopPropagation(); + setC1Down(true); + }} + onPointerUp={e => { + setC1Down(false); + }} + /> + {/* Top right */} + <line x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${cPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - cPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" /> + <circle + cx={`${cPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} + cy={`${EDITOR_WIDTH - cPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} + r="5" + fill={`${c2Down ? '#3fa9ff' : '#ffffff'}`} + onPointerDown={e => { + e.stopPropagation(); + setC2Down(true); + }} + onPointerUp={e => { + setC2Down(false); + }} + /> + </svg> + </div> + ); +}; + +export default CubicBezierEditor; diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss index dd0007d82..0aceb1b28 100644 --- a/src/client/views/nodes/trails/PresBox.scss +++ b/src/client/views/nodes/trails/PresBox.scss @@ -28,6 +28,20 @@ overflow-y: hidden; } +// Bezier editor + +.presBox-bezier-editor { + border: 1px solid rgb(221, 221, 221); + border-radius: 4px; +} + +.presBox-easing-function-options { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + .presBox-cont { cursor: auto; position: absolute; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index cd3114940..cc05a5c5f 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -43,6 +43,7 @@ import { BiMicrophone, BiX } from 'react-icons/bi'; import { AiOutlineSend } from 'react-icons/ai'; import { gptSlideProperties, gptTrailSlideCustomization } from '../../../apis/gpt/customization'; import { DictationManager } from '../../../util/DictationManager'; +import CubicBezierEditor from './CubicBezierEditor'; export interface pinDataTypes { scrollable?: boolean; dataviz?: number[]; @@ -91,6 +92,10 @@ const easeItems = [ text: 'Linear', val: 'linear', }, + // { + // text: 'Custom', + // val: 'custom', + // }, ]; @observer @@ -162,6 +167,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.isRecording = isRecording; }; + // Easing function variables + + @observable easeDropdownVal = 'ease'; + + // @observable bezierControlPoints: { p1: number[]; p2: number[] } = { p1: [0.67, 0.2], p2: [0.37, 0.88] }; + @action setBezierControlPoints = (newPoints: { p1: number[]; p2: number[] }) => { + // this.bezierControlPoints = newPoints; + this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`); + }; + @computed get isTreeOrStack() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any); @@ -2021,20 +2036,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="slider-text">Slow</div> </div> {/* Easing function */} - <Dropdown - color={StrCast(Doc.UserDoc().userColor)} - formLabel={'Easing Function'} - closeOnSelect={true} - items={easeItems} - selectedVal={this.activeItem.presEaseFunc ? StrCast(this.activeItem.presEaseFunc) : 'ease'} - setSelectedVal={val => { - if (typeof val === 'string') this.setEaseFunc(this.activeItem, val); - }} - dropdownType={DropdownType.SELECT} - type={Type.TERT} - fillWidth - /> - {/* Custom */} + <div className="presBox-easing-function-options"> + <Dropdown + color={StrCast(Doc.UserDoc().userColor)} + formLabel={'Easing Function'} + closeOnSelect={true} + items={easeItems} + selectedVal={this.activeItem.presEaseFunc ? StrCast(this.activeItem.presEaseFunc) : 'ease'} + setSelectedVal={val => { + if (typeof val === 'string') this.setEaseFunc(this.activeItem, val); + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + // fillWidth + /> + {/* Custom */} + <p className="presBox-submenu-label">Custom Timing Function</p> + <CubicBezierEditor setFunc={this.setBezierControlPoints} /> + </div> </div> <div className="ribbon-box"> Effects |