Skip to content

Commit

Permalink
feat(model3d): Adding animation clips flyout
Browse files Browse the repository at this point in the history
  • Loading branch information
Conrad Chan committed May 4, 2021
1 parent 3164ac3 commit 240fc99
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 23 deletions.
14 changes: 14 additions & 0 deletions src/lib/viewers/box3d/model3d/Model3DRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,20 @@ class Model3DRenderer extends Box3DRenderer {
initVrGamepadControls() {
this.vrControls = new Model3DVrControls(this.vrGamepads, this.box3d);
}

/**
* Gets any selected animation clip
*
* @return {string} selected animation clip id
*/
getAnimationClip() {
if (!this.instance) {
return '';
}

const component = this.instance.getComponentByScriptId('animation');
return component.clipId;
}
}

export default Model3DRenderer;
24 changes: 15 additions & 9 deletions src/lib/viewers/box3d/model3d/Model3DViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ class Model3DViewer extends Box3DViewer {
*/
handleSelectAnimationClip(clipId) {
this.renderer.setAnimationClip(clipId);

if (this.controls && this.getViewerOption('useReactControls')) {
this.setAnimationState(false);
this.renderUI();
}
}

/**
Expand Down Expand Up @@ -294,18 +299,18 @@ class Model3DViewer extends Box3DViewer {
* @return {void}
*/
handleToggleAnimation(play) {
if (this.getViewerOption('useReactControls')) {
this.isAnimationPlaying = !this.isAnimationPlaying;
this.renderer.toggleAnimation(this.isAnimationPlaying);
this.setAnimationState(play);

if (this.controls) {
this.renderUI();
}
} else {
this.renderer.toggleAnimation(play);
if (this.controls && this.getViewerOption('useReactControls')) {
this.renderUI();
}
}

setAnimationState(play) {
this.isAnimationPlaying = play;
this.renderer.toggleAnimation(this.isAnimationPlaying);
}

/**
* Handle canvas focus events.
* @method handleCanvasClick
Expand Down Expand Up @@ -422,13 +427,14 @@ class Model3DViewer extends Box3DViewer {
}

renderUI() {
if (!this.controls) {
if (!this.controls || !this.renderer) {
return;
}

this.controls.render(
<Model3DControlsNew
animationClips={this.animationClips}
currentAnimationClipId={this.renderer.getAnimationClip()}
isPlaying={this.isAnimationPlaying}
onAnimationClipSelect={this.handleSelectAnimationClip}
onFullscreenToggle={this.toggleFullscreen}
Expand Down
23 changes: 23 additions & 0 deletions src/lib/viewers/controls/model3d/AnimationClipsControl.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.bp-AnimationClipsControl {
position: relative;
display: flex;
align-items: center;

.bp-SettingsFlyout {
right: auto;
left: 0;
max-height: 200px;
overflow-x: hidden;
overflow-y: auto;
}
}

.bp-AnimationClipsControl-radioItem {
.bp-SettingsRadioItem-value {
max-width: 183px;
overflow-x: hidden;
font-size: 13px;
white-space: nowrap;
text-overflow: ellipsis;
}
}
49 changes: 43 additions & 6 deletions src/lib/viewers/controls/model3d/AnimationClipsControl.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import AnimationClipsToggle from './AnimationClipsToggle';
import Settings, { Menu } from '../settings';
import './AnimationClipsControl.scss';

type AnimationClip = {
duration: number;
Expand All @@ -13,12 +15,47 @@ export type Props = {
onAnimationClipSelect: () => void;
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function AnimationClipsControl(props: Props): JSX.Element {
export const padLeft = (x: number, width: number): string => {
return x.length >= width ? x : new Array(width - x.length + 1).join('0') + x;
};

export const formatDurationStr = (duration: number): string => {
let secondsLeft = Math.floor(duration);
const hours = Math.floor(secondsLeft / 3600);
const hoursStr = padLeft(hours.toString(), 2);

secondsLeft -= hours * 3600;
const minutes = Math.floor(secondsLeft / 60);
const minutesStr = padLeft(minutes.toString(), 2);

secondsLeft -= minutes * 60;
const secondsStr = padLeft(secondsLeft.toString(), 2);

return `${hoursStr}:${minutesStr}:${secondsStr}`;
};

export default function AnimationClipsControl({
animationClips,
currentAnimationClipId,
onAnimationClipSelect,
}: Props): JSX.Element {
return (
<>
<AnimationClipsToggle />
{/* TODO: AnimationClipsFlyout */}
</>
<Settings className="bp-AnimationClipsControl" disableTransitions={false} icon={AnimationClipsToggle}>
<Settings.Menu name={Menu.MAIN}>
{animationClips.map(({ duration, id, name }) => {
const isSelected = id === currentAnimationClipId;
return (
<Settings.RadioItem
key={id}
className="bp-AnimationClipsControl-radioItem"
isSelected={isSelected}
label={`${formatDurationStr(duration)} ${name}`}
onChange={onAnimationClipSelect}
value={id}
/>
);
})}
</Settings.Menu>
</Settings>
);
}
14 changes: 12 additions & 2 deletions src/lib/viewers/controls/model3d/AnimationClipsToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ export type Props = {
onClick?: () => void;
};

export default function AnimationClipsToggle({ onClick }: Props): JSX.Element {
function AnimationClipsToggle(props: Props, ref: React.Ref<HTMLButtonElement>): JSX.Element {
const { onClick } = props;

return (
<button className="bp-AnimationClipsToggle" onClick={onClick} title={__('box3d_animation_clips')} type="button">
<button
ref={ref}
className="bp-AnimationClipsToggle"
onClick={onClick}
title={__('box3d_animation_clips')}
type="button"
>
<IconAnimation24 />
</button>
);
}

export default React.forwardRef(AnimationClipsToggle);
16 changes: 13 additions & 3 deletions src/lib/viewers/controls/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ import { decodeKeydown } from '../../../util';

export type Props = React.PropsWithChildren<{
className?: string;
disableTransitions?: boolean;
icon?: React.ReactNode;
}>;

export default function Settings({ children, className, ...rest }: Props): JSX.Element | null {
export default function Settings({
children,
className,
disableTransitions = false,
icon: ToggleIcon = SettingsToggle,
...rest
}: Props): JSX.Element | null {
const [activeMenu, setActiveMenu] = React.useState(Menu.MAIN);
const [activeRect, setActiveRect] = React.useState<Rect>();
const [isFocused, setIsFocused] = React.useState(false);
Expand Down Expand Up @@ -79,8 +87,10 @@ export default function Settings({ children, className, ...rest }: Props): JSX.E
{...rest}
>
<SettingsContext.Provider value={{ activeMenu, activeRect, setActiveMenu, setActiveRect }}>
<SettingsToggle ref={buttonElRef} isOpen={isOpen} onClick={handleClick} />
<SettingsFlyout isOpen={isOpen}>{children}</SettingsFlyout>
<ToggleIcon ref={buttonElRef} isOpen={isOpen} onClick={handleClick} />
<SettingsFlyout disableTransitions={disableTransitions} isOpen={isOpen}>
{children}
</SettingsFlyout>
</SettingsContext.Provider>
</div>
);
Expand Down
12 changes: 9 additions & 3 deletions src/lib/viewers/controls/settings/SettingsFlyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import './SettingsFlyout.scss';

export type Props = React.PropsWithChildren<{
className?: string;
disableTransitions?: boolean;
isOpen: boolean;
}>;

export default function SettingsFlyout({ children, className, isOpen }: Props): JSX.Element {
export default function SettingsFlyout({
children,
className,
disableTransitions = false,
isOpen,
}: Props): JSX.Element {
const [isTransitioning, setIsTransitioning] = React.useState(false);
const flyoutElRef = React.useRef<HTMLDivElement>(null);
const { activeRect } = React.useContext(SettingsContext);
const { height, width } = activeRect || { height: 'auto', width: 'auto' };
const { activeRect = { height: 'auto', width: 'auto' } } = React.useContext(SettingsContext);
const { height, width } = disableTransitions ? { height: 'auto', width: 'auto' } : activeRect;

React.useEffect(() => {
const { current: flyoutEl } = flyoutElRef;
Expand Down

0 comments on commit 240fc99

Please sign in to comment.